Projekt: Installer z svn, mvn i spring-boot

Każda aplikacja warta lub nie warta uwagi ale posiadająca aspiracje ma dziś własny instalator. W dzisiejszym odcinku zajmiemy się stworzenie narzędzia do instalacji naszego projektu. Będzie to plik wykonywalny typu EXE. Jego zadaniem będzie zebraniem informacji od użytkownika o przeczytaniu licencji (zaznaczeniu odpowiedniego kwadracika), zadecydowanie o utworzeniu ikony na pulpicie, wybranie miejsca na dysku gdzie ma zostać zainstalowana aplikacja (plik jar), oraz na samym końcu uruchomienie naszej aplikacji.
Do tej prezentacji wybrałem bardzo prostą opcję czyli zagnieździłem plik .jar w pliku .exe, wiem że dziś mamy dobrodziejstwo internetu i możemy sobie spokojnie pobrać zasoby. Sypię głowę popiołem ale naszym celem jest uruchomienie aplikacji a nie sprzeczanie się nad tym co można 😉
Instalator jest bardzo prosty i to jest jego celem. Jeśli ktoś ma chęć rozszerzyć projekt o nowe możliwości to proszę bardzo, będzie mi miło że dałem komuś natchnienie.

Zapraszam do lektury i komentarzy!

Zacznijmy od wyboru technologii

L.P. Co Więcej
1 repo* http://git.e-strix.com/sample_installer.git/
2 Java 1.8.0
3 Maven 3.3.9

*) W konfiguracji projektu (plik pom.xml) jest oznaczony svn, natomiast aktualnie repo znajduje się na prywatnym serwerze git. Powodem jest fakt że nie opłaca stawiać mi się stawiać svn’a dla jednego projektu. W załączonym filmie będę pracował na SVN i myślę że to wystarczy. Jeśli jesteś zainteresowany konfiguracją SVN zapraszam tutaj

1. Zaczynajmy: Struktura projektu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
│   LICENSE.md
│   pom.xml
│   README.md

└───src
  └───main
    ├───java
    │  └───pl
    │    └───estrix
    │      └───javafx
    │        AppContext.java
    │        FilterProp.java
    │        InstallerService.java
    │        JavaApplication.java
    │        MainController.java
    │        Step1Controller.java
    │        Step2Controller.java
    │        Step3Controller.java
    │
    └───resources
      │    main.fxml
      │    step_1.fxml
      │    step_2.fxml
      │    step_3.fxml
      │
      ├───content
      │    application.ico
      │    config.bat
      │    JavaFxGitSample_5.0.0.jar
      │    sudo.cmd
      │
      ├───img
      │    application.png
      │    logo.png
      │
      └───META-INF
           spring.factories

Nie będę omawiał każdego pliku, nie ma na to czasu, zwrócę uwagę tylko na te, które wnoszą coś ciekawego. Są to dość proste rzeczy, jeśli masz wątpliwości to proszę wróć do podstaw programowania w java i javaFx. Jest wiele dobrych materiałów na YouTube o tym temacie, również w języku polskim. Mój ulubiony to kanał „Zacznij Programować”, polecam do zapoznania się 🙂

Cały projekt można pobrać z odnośnika repozytorium.

Plik: pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?xml version="1.0" encoding="ISO-8859-2"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.estrix</groupId>
  <artifactId>estrix-jar-launcher</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>launcher</name>
  <url>http://maven.apache.org</url>

  <profiles>
    <profile>
      <id>windows-deploy</id>
      <activation>
        <os>
          <family>Windows</family>
        </os>
      </activation>
      <build>
        <plugins>
          <plugin>
            <groupId>com.akathist.maven.plugins.launch4j</groupId>
            <artifactId>launch4j-maven-plugin</artifactId>
            <version>1.7.22</version>
            <executions>
              <execution>
                <id>l4j-clui</id>
                <phase>package</phase>
                <goals>
                  <goal>launch4j</goal>
                </goals>
                <configuration>
                  <headerType>gui</headerType>
                  <outfile>target/JavaFxGitSample-win-install.exe</outfile>
                  <jar>target/estrix-jar-launcher-1.0-SNAPSHOT.jar</jar>
                  <classPath>
                    <mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
                    <preCp>anything</preCp>
                  </classPath>

                  <icon>src/main/resources/content/application.ico</icon>
                  <jre>
                    <minVersion>1.8.0</minVersion>
                    <jdkPreference>preferJre</jdkPreference>
                  </jre>

                  <versionInfo>
                    <fileVersion>1.0.0.0</fileVersion>
                    <txtFileVersion>${project.version}</txtFileVersion>
                    <fileDescription>${project.name}</fileDescription>
                    <copyright>2018 e-Strix.com</copyright>
                    <productVersion>1.0.0.0</productVersion>
                    <txtProductVersion>1.0.0.0</txtProductVersion>
                    <productName>${project.name}</productName>
                    <companyName>e-Strix Kamil Mucik</companyName>
                    <internalName>e-Strix Kamil Mucik</internalName>
                    <originalFilename>JavaFxGitSample-win-install.exe</originalFilename>
                  </versionInfo>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>

  <scm>
    <connection>scm:svn:http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk</connection>
    <developerConnection>scm:svn:http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk</developerConnection>
    <url>http://37.187.242.134/svn/estrix-javafx/013-project-for-YT-movie/svn_mvn/trunk</url>
  </scm>

  <distributionManagement>
    <repository>
      <uniqueVersion>false</uniqueVersion>
      <id>corp1</id>
      <name>Corporate Repository</name>
      <url>file://C:\temp</url>
      <!--<url>scp://repo/maven2</url>-->
      <layout>default</layout>
    </repository>
  </distributionManagement>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring.boot.mainClass>pl.estrix.javafx.JavaApplication</spring.boot.mainClass>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-io</artifactId>
      <version>1.3.2</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <executable>true</executable>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <phase>generate-resources</phase>
            <goals>
              <goal>run</goal>
            </goals>
            <configuration>
              <tasks>
                <mkdir dir="${project.build.directory}" />
                <tstamp>
                  <format property="last.updated" pattern="yyyy.MM.dd HH:mm" />
                </tstamp>
                <echo file="${basedir}/target/classes/filter.properties" append="false" message="estrix.application.biuld-time=${last.updated}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="${line.separator}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="estrix.application.name=${project.name}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="${line.separator}" />
                <echo file="${basedir}/target/classes/filter.properties" append="true" message="estrix.application.version=${project.version}" />
              </tasks>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Plik: src\main\java\pl\estrix\javafx\AppContext.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package pl.estrix.javafx;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.stage.Stage;
import org.springframework.context.ConfigurableApplicationContext;

class AppContext {

    /**
     * Path where app will be installed.
     */

    static String targetPath = "C:\\Users\\TEST\\AppData\\Local\";

    /**
     * No comment.
     */
    static Boolean createShortcut = false;

    static ConfigurableApplicationContext springContext;

    static Boolean finish = Boolean.FALSE;

    static Stage primaryStage;

    static BooleanProperty licenseAgreement = new SimpleBooleanProperty(Boolean.FALSE);
    static BooleanProperty buttonNextProperty = new SimpleBooleanProperty(Boolean.FALSE);
    static BooleanProperty buttonPrevProperty = new SimpleBooleanProperty(Boolean.FALSE);


}

Plik: src\main\java\pl\estrix\javafx\FilterProp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package pl.estrix.javafx;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("filter.properties")
class FilterProp {

    @Value("${estrix.application.name}")
    private String name ;
    @Value("${estrix.application.version}")
    private String version ;
    @Value("${estrix.application.biuld-time}")
    private String biuldTime ;

    String getName() {
        return name;
    }

    String getVersion() {
        return version;
    }

    String getBiuldTime() {
        return biuldTime;
    }

}

Plik: src\main\java\pl\estrix\javafx\InstallerService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package pl.estrix.javafx;

import org.apache.commons.io.FileUtils;

import java.io.*;
import java.net.URL;

class InstallerService {

    static void createFolder(String path) {
        try {
            path += "_SampleApp/";
            boolean success = (new File(path)).mkdirs();

            if (success) {
                AppContext.targetPath = path;
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    static void createSettings(){
        try {
            PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(AppContext.targetPath + "settings.bat", true)));
            out.println("set app_path="+AppContext.targetPath);
            out.println("set app_desktop_shortcut="+AppContext.createShortcut);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void copyResource(String file){
        URL inputUrl = InstallerService.class.getResource("/content/" + file);
        File dest = new File(AppContext.targetPath + "/" + file);
        try {
            FileUtils.copyURLToFile(inputUrl, dest);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void executeBatch(){
        try {
            Runtime.getRuntime().exec("cmd /c config.bat", null, new File(AppContext.targetPath));
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }

    static void launchApp(){
        try {
            Runtime.getRuntime().exec("javaw.exe -jar JavaFxGitSample_5.0.0.jar", null, new File(AppContext.targetPath));
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }
}

Plik: src\main\java\pl\estrix\javafx\JavaApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package pl.estrix.javafx;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;

@SpringBootApplication
@ComponentScan(basePackages = "pl.estrix.javafx")
@PropertySource("classpath:filter.properties")
@Configuration
public class JavaApplication extends Application {

    private ConfigurableApplicationContext context;
    private Parent rootNode;

    public static void main(final String[] args) {
        Application.launch(args);
    }

    @Override
    public void init() throws Exception {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(JavaApplication.class);
        context = builder.run(getParameters().getRaw().toArray(new String[0]));

        AppContext.springContext = context;
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"));
        loader.setControllerFactory(AppContext.springContext::getBean);
        rootNode = loader.load();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(rootNode, 580, 420));
        primaryStage.centerOnScreen();
        primaryStage.show();

        Image icon = new Image(JavaApplication.class.getResourceAsStream("/img/application.png"));
        primaryStage.getIcons().add(icon);

        AppContext.primaryStage = primaryStage;
    }

    @Override
    public void stop() throws Exception {
        AppContext.springContext.close();
        Platform.exit();
    }

}

Plik: src\main\java\pl\estrix\javafx\MainController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.IOException;

@Controller
public class MainController {

    @Autowired
    private FilterProp filterProp;
    @Autowired
    private JavaApplication javaApplication;

    @FXML
    private BorderPane borderPane;
    @FXML
    private Button nextButton;
    @FXML
    private Button prevButton;
    @FXML
    private Label infoBuild;
    @FXML
    private Label infoName;
    @FXML
    private Label infoVersion;

    @FXML
    public void initialize() {
        nextButton.disableProperty().bind(AppContext.buttonNextProperty.not());
        prevButton.disableProperty().bind(AppContext.buttonPrevProperty.not());

        infoBuild.setText(filterProp.getBiuldTime());
        infoName.setText(filterProp.getName());
        infoVersion.setText(filterProp.getVersion());

        step1();
    }

    private void setCenter(String fxmlPath)  {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath));
            loader.setControllerFactory(AppContext.springContext::getBean);
            AnchorPane node = loader.load();

            borderPane.setCenter( node);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void step1() {setCenter("/step_1.fxml"); }

    private void step2() {setCenter("/step_2.fxml"); }

    void step3() {setCenter("/step_3.fxml"); }

    public void onPrevAction() {
        step1();
    }

    public void onNextAction() {
        step2();
    }

    void onCloseAction() {
        try {
            javaApplication.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Plik: src\main\java\pl\estrix\javafx\Step1Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.RadioButton;
import org.springframework.stereotype.Controller;

@Controller
public class Step1Controller {

    @FXML
    private RadioButton agreementCheckYes;

    @FXML
    public void initialize() {
        agreementCheckYes.selectedProperty().addListener( (object, oldValue, newValue) ->{
            AppContext.licenseAgreement.setValue(newValue);
            AppContext.buttonNextProperty.setValue(newValue);
        });

        AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
        agreementCheckYes.selectedProperty().setValue(AppContext.licenseAgreement.getValue());
    }
}

Plik: src\main\java\pl\estrix\javafx\Step2Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package pl.estrix.javafx;

import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import java.io.File;

@Controller
public class Step2Controller {

    @Autowired
    private MainController mainController;

    @FXML
    private TextField textField2;
    @FXML
    private ProgressBar progressBar;
    @FXML
    private CheckBox createShortcut;
    @FXML
    private Button installButton;
    @FXML
    private Button changeDirButton;

    @FXML
    public void initialize() {
        AppContext.buttonPrevProperty.setValue(Boolean.TRUE);
        AppContext.buttonNextProperty.setValue(Boolean.FALSE);

        textField2.setText(AppContext.targetPath);

        createShortcut.selectedProperty().addListener( ((observable, oldValue, newValue) -> {
            AppContext.createShortcut = newValue;
        }));

        Platform.runLater( () -> {
            installButton.requestFocus();
        });
    }

    public void onPathChangeAction() {
        DirectoryChooser chooser = new DirectoryChooser();
        chooser.setTitle("Wybierz folder aplikacji");
        File defaultDirectory = new File(AppContext.targetPath);
        chooser.setInitialDirectory(defaultDirectory);
        File selectedDirectory = chooser.showDialog(AppContext.primaryStage);
        if (selectedDirectory != null) {
            AppContext.targetPath = selectedDirectory.getAbsolutePath();
        } else {
            AppContext.targetPath = textField2.getPromptText();
        }
        textField2.setText(AppContext.targetPath);
    }

    public void onInstallAction() {
        Task copyWorker = createWorker();
        progressBar.progressProperty().unbind();
        progressBar.progressProperty().bind(copyWorker.progressProperty());
        installButton.disableProperty().setValue(Boolean.TRUE);
        changeDirButton.disableProperty().setValue(Boolean.TRUE);
        createShortcut.disableProperty().setValue(Boolean.TRUE);

        copyWorker.messageProperty().addListener((observable, oldValue, newValue) ->
                System.out.println(newValue)
        );
        copyWorker.stateProperty().addListener((observable, oldValue, newValue) -> {
            if (Worker.State.SUCCEEDED.equals(newValue)){
                AppContext.finish = true;
                AppContext.buttonNextProperty.setValue(Boolean.TRUE);
                AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
                mainController.step3();
            }
        });

        new Thread(copyWorker).start();
    }


    private Task createWorker() {
        return new Task() {
            @Override
            protected Object call() throws Exception {
                updateProgress(1,10);
                InstallerService.createFolder(AppContext.targetPath);
                updateProgress(2,10);
                InstallerService.createSettings();
                updateProgress(3,10);
                InstallerService.copyResource("sudo.cmd");
                updateProgress(4,10);
                InstallerService.copyResource("JavaFxGitSample_5.0.0.jar");
                updateProgress(5,10);
                InstallerService.copyResource("application.ico");
                updateProgress(6,10);
                InstallerService.copyResource("config.bat");
                updateProgress(7,10);
                InstallerService.executeBatch();

                updateProgress(10,10);
                return true;
            }
        };
    }

}

Plik: src\main\java\pl\estrix\javafx\Step3Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class Step3Controller {

    @Autowired
    private MainController mainController;

    @FXML
    private CheckBox launchApp;

    @FXML
    public void initialize() {
        AppContext.buttonPrevProperty.setValue(Boolean.FALSE);
        AppContext.buttonNextProperty.setValue(Boolean.FALSE);
    }

    public void onExitAppAction() {
        if (launchApp.isSelected()) {
            InstallerService.launchApp();
        }

        mainController.onCloseAction();
    }

}

Plik: src\main\resources\main.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="420.0" prefWidth="580.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.MainController">
   <children>
      <VBox prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
         <children>
            <HBox alignment="CENTER" style="-fx-background-color: #ffffff;">
               <children>
                  <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="380.0" prefWidth="160.0" HBox.hgrow="ALWAYS">
                     <children>
                        <ImageView fitHeight="380.0" fitWidth="160.0" pickOnBounds="true" preserveRatio="true" AnchorPane.leftAnchor="0.0">
                           <image>
                              <Image url="@img/logo.png" />
                           </image>
                        </ImageView>
                        <Label fx:id="infoVersion" layoutX="35.0" layoutY="318.0" text="Label" AnchorPane.bottomAnchor="4.0" AnchorPane.leftAnchor="8.0" />
                        <Label fx:id="infoName" layoutY="327.0" text="Label" AnchorPane.bottomAnchor="24.0" AnchorPane.leftAnchor="8.0" />
                        <Label fx:id="infoBuild" layoutX="10.0" layoutY="337.0" text="Label" AnchorPane.bottomAnchor="44.0" AnchorPane.leftAnchor="8.0" />
                     </children>
                  </AnchorPane>
                  <VBox maxHeight="1.7976931348623157E308" prefWidth="217.0" style="-fx-background-color: #00cd6b;" HBox.hgrow="ALWAYS">
                     <children>
                        <Label alignment="CENTER" contentDisplay="CENTER" maxWidth="1.7976931348623157E308" text="e-Strix.com" textAlignment="CENTER" textFill="WHITE">
                           <VBox.margin>
                              <Insets bottom="10.0" top="10.0" />
                           </VBox.margin>
                        </Label>
                        <AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-background-color: #f0f0f0;" VBox.vgrow="ALWAYS">
                           <children>
                              <BorderPane fx:id="borderPane" layoutX="106.0" layoutY="61.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
                           </children>
                        </AnchorPane>
                     </children>
                  </VBox>
               </children>
               <cursor>
                  <Cursor fx:constant="DEFAULT" />
               </cursor>
            </HBox>
            <AnchorPane maxWidth="1.7976931348623157E308" prefHeight="200.0">
               <children>
                  <Button fx:id="prevButton" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" onAction="#onPrevAction" prefWidth="100.0" text="Wstecz" AnchorPane.leftAnchor="178.0" AnchorPane.topAnchor="8.0" />
                  <Button fx:id="nextButton" alignment="CENTER" contentDisplay="CENTER" mnemonicParsing="false" onAction="#onNextAction" prefWidth="100.0" text="Dalej" textAlignment="CENTER" AnchorPane.rightAnchor="18.0" AnchorPane.topAnchor="8.0" />
               </children>
            </AnchorPane>
         </children>
      </VBox>
   </children>
</AnchorPane>

Plik: src\main\resources\step_1.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="340.0" prefWidth="420.0" style="-fx-background-color: #f0f0f0;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.Step1Controller">
   <children>
      <TextArea layoutX="11.0" layoutY="14.0" promptText="Licencja" AnchorPane.bottomAnchor="50.0" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="8.0" AnchorPane.topAnchor="8.0" />
      <RadioButton layoutX="14.0" layoutY="281.0" mnemonicParsing="false" selected="true" text="Nie zgadzam się" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="8.0">
         <toggleGroup>
            <ToggleGroup fx:id="accept" />
         </toggleGroup>
      </RadioButton>
      <RadioButton fx:id="agreementCheckYes" layoutX="14.0" layoutY="309.0" mnemonicParsing="false" text="Zgadzam się" toggleGroup="$accept" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="8.0" />
   </children>
</AnchorPane>

Plik: src\main\resources\step_2.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="340.0" prefWidth="420.0" style="-fx-background-color: #f0f0f0;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.Step2Controller">
   <children>
      <Label layoutX="43.0" layoutY="39.0" text="step 2" AnchorPane.leftAnchor="8.0" AnchorPane.topAnchor="24.0" />
      <TextField fx:id="textField2" disable="true" editable="false" layoutX="43.0" layoutY="70.0" prefHeight="25.0" prefWidth="284.0" promptText="C:\Program Files\JavaFxSample" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="64.0" AnchorPane.topAnchor="48.0" />
      <ProgressBar fx:id="progressBar" layoutX="8.0" layoutY="300.0" prefHeight="26.0" prefWidth="200.0" progress="0.0" AnchorPane.bottomAnchor="22.0" AnchorPane.leftAnchor="8.0" AnchorPane.rightAnchor="64.0" />
      <Separator layoutY="273.0" prefWidth="200.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" />
      <Button fx:id="installButton" layoutX="344.0" layoutY="292.0" mnemonicParsing="false" onAction="#onInstallAction" text="Instaluj" AnchorPane.rightAnchor="8.0" />
      <CheckBox fx:id="createShortcut" layoutX="40.0" layoutY="114.0" mnemonicParsing="false" text="Utwórz ikonę na pulpicie" AnchorPane.leftAnchor="8.0" AnchorPane.topAnchor="84.0" />
      <Button fx:id="changeDirButton" layoutX="345.0" layoutY="70.0" mnemonicParsing="false" onAction="#onPathChangeAction" text="Zmień" AnchorPane.rightAnchor="8.0" AnchorPane.topAnchor="48.0" />
   </children>
</AnchorPane>

Plik: src\main\resources\step_3.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="340.0" prefWidth="420.0" style="-fx-background-color: #f0f0f0;" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.Step3Controller">
    <children>
        <Label layoutX="61.0" layoutY="35.0" text="Podsumowanie" AnchorPane.leftAnchor="24.0" AnchorPane.topAnchor="24.0" />
      <Button layoutX="120.0" layoutY="155.0" mnemonicParsing="false" onAction="#onExitAppAction" prefHeight="30.0" prefWidth="180.0" text="Zakończ" />
      <CheckBox fx:id="launchApp" layoutX="44.0" layoutY="170.0" mnemonicParsing="false" text="Uruchom aplikacje" AnchorPane.leftAnchor="24.0" AnchorPane.topAnchor="64.0" />
    </children>
</AnchorPane>

Plik: src\main\resources\content\config.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@echo off

:configloader
call settings.bat
:configloaderends

set mypath=%cd%
set binPath=%mypath%\bin
if not exist "%binPath%" (
    mkdir  %binPath%
)

rem :::::::: CREATE Menu Start Icon
echo Set oWS = WScript.CreateObject("WScript.Shell") > %mypath%\CreateShortcut.vbs
echo sLinkFile = "%mypath%\bin\JavaFxGitSample.lnk" >> %mypath%\CreateShortcut.vbs
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %mypath%\CreateShortcut.vbs
echo oLink.TargetPath = "%mypath%\JavaFxGitSample_5.0.0.jar" >> %mypath%\CreateShortcut.vbs
echo oLink.WorkingDirectory = "%mypath%" >> %mypath%\CreateShortcut.vbs
echo oLink.Description = "JavaFxGitSample" >> %mypath%\CreateShortcut.vbs
echo oLink.IconLocation = "%mypath%\application.ico" >> %mypath%\CreateShortcut.vbs
echo oLink.Save >> CreateShortcut.vbs
cscript CreateShortcut.vbs
del CreateShortcut.vbs

if "%app_desktop_shortcut%" == "true" (
    copy %mypath%\bin\JavaFxGitSample.lnk %userprofile%\Desktop\JavaFxGitSample.lnk
)

sudo.cmd  xcopy  %mypath%\bin %ProgramData%\Microsoft\Windows""Start Menu""\Programs\e-Strix\

Plik: src\main\resources\content\sudo.cmd

1
2
3
4
@echo Set objShell = CreateObject("Shell.Application") > %temp%\sudo.tmp.vbs
@echo args = Right("%*", (Len("%*") - Len("%1"))) >> %temp%\sudo.tmp.vbs
@echo objShell.ShellExecute "%1", args, "", "runas" >> %temp%\sudo.tmp.vbs
@cscript %temp%\sudo.tmp.vbs

Plik: src\main\resources\META-INF\spring.factories

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
pl.estrix.javafx.JavaApplication

Projekt: git gradle spring-boot

http://git.e-strix.com/sample_fx_app.git/

W każdym (nawet freelancer’skim) projekcie przychodzi moment na to aby zacząć pisać kod i tu już dostajemy wiatr w żagiel i stopy wody pod kilem. W zasadzie ogranicza nas tylko lenistwo z jednej strony i kreatywność z drugiej.

Dla potrzeb prezentacji wybiorę sobie coś na czasie, a będzie to:

  • spring-boot
  • gradle
  • git
  • javafx

A zatem bez zbędnych patosów, teorii i literatury zacznijmy od stworzenia kilku plików.

Tworzenie projektu

Zacznijmy od utworzenia repozytorium. Jeśli nie widziałeś mojego wpisu na ten temat to zobacz pierw: php7-git-gitlist.

A teraz do rzeczy. Struktura projektu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└───javafx-git-sample
    │     .gitignore
    │     build.gradle
    │     latest.properties
    │     settings.gradle
    │
    ├───gradle
    │        release.gradle
    │
    └───src
        └───main
            ├───java
            │    └───pl
            │        └───estrix
            │            └───javafx
            │                    FilterProp.java
            │                    JavaApplication.java
            │                    MainController.java
            │
            └───resources
                │     logo.png
                │     main.fxml
                │
                └───META-INF
                        spring.factories

Plik .gitignore

1
2
3
4
.gradle/
.idea/
build/
*.iml

Plik build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE")
    }
}

apply from: 'gradle/release.gradle'
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'


bootJar {
    mainClassName = 'pl.estrix.javafx.JavaApplication'
    manifest {
        attributes 'Start-Class': 'pl.estrix.javafx.JavaApplication'
    }
}

springBoot {
    mainClassName = 'pl.estrix.javafx.JavaApplication'
}

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    testCompile("junit:junit")
}


def buildTime() {
    new Date().format('yyyy.MM.dd HH:mm')
}

def generatedResources = "$buildDir/resources/main/"
sourceSets {
    main {
        output.dir(generatedResources, builtBy: 'generateMyResources')
    }
}

task generateMyResources {
    doLast {
        def generatedTmp = new File(generatedResources)
        if( !generatedTmp.exists() ) {
            generatedTmp.mkdirs()
        }
        def generated = new File(generatedResources, "filter.properties")

        generated.text = "estrix.application.biuld-time=${buildTime()}\n" +
                "estrix.application.name=${project.name}\n" +
                "estrix.application.version=${project.version}\n"
    }
}

Plik latest.properties

1
2
3
4
#Tue Apr 10 16:59:22 CEST 2018
bugfix=0
major=1
minor=0

Plik settings.gradle

1
rootProject.name = 'JavaFxGitSample'

Plik gradle/release.gradle

Notatka: Pomysł na zarządzanie wersjami pochodzi z https://www.youtube.com/watch?v=Y6SVoXFsw7I

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Version {
    int major
    int minor
    int bugfix
    boolean snapshot = true

    static Version load(File versionFile){
        def props = new Properties()
        versionFile.withInputStream {
            stream -> props.load(stream)
        }
        def major = Integer.parseInt(props['major'])
        def minor = Integer.parseInt(props['minor'])
        def bugfix = Integer.parseInt(props['bugfix'])
        return new Version(major:major, minor:minor, bugfix:bugfix)
    }

    String toString(){
        "${major}.${minor}.${bugfix}${snapshot ? '-SNAPSHOT':''}"
    }

    void store(File versionFile){
        def props = new Properties()
        props['bugfix'] =  bugfix.toString()
        props['major']  =  major.toString()
        props['minor']  =  minor.toString()
        props.store(versionFile.newWriter(), null)
    }

    Version next(String releaseType = "bugfix"){
        if(releaseType == "major"){
            return new Version(major:major+1, minor:0, bugfix:0)
        }else if(releaseType == "minor"){
            return new Version(major:major, minor:minor+1, bugfix:0)
        }else{
            return new Version(major:major, minor:minor, bugfix:bugfix+1)
        }
    }
}

def latest = Version.load(rootProject.file("latest.properties"))
allprojects{
    version = latest.next()
}
gradle.taskGraph.whenReady { graph ->
    if(graph.hasTask(":release")){
        allprojects{
            version = latest.next(release.type)
            version.snapshot = false
        }
    }
}

task persistVersionInfo(type:Exec){
    commandLine "git" , "commit","-i", "latest.properties", "-m", "' update version to ${-> version }'"
    doFirst{
      version.store(file("latest.properties"))
   }
}

task tagWorkspace(type:Exec){  
   commandLine 'git', 'tag', "${-> version }"
   commandLine 'git', 'push', 'origin',  'HEAD:master'
}

task release(type:Release){
    type = System.getProperty("type")
    dependsOn  "build", "persistVersionInfo", "tagWorkspace"
}

import org.gradle.api.internal.tasks.options.Option

class Release extends DefaultTask{
    @Option(option="type", description="the type of a release.")
    String type = "bugfix"

    @TaskAction void release(){
        println "doing a $type release of ${project.version}"
    }
}

Plik src/main/java/pl/estrix/javafx/FilterProp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package pl.estrix.javafx;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties("filter.properties")
public class FilterProp {

    @Value("${estrix.application.name}")
    private String name;
    @Value("${estrix.application.version}")
    private String version;
    @Value("${estrix.application.biuld-time}")
    private String biuldTime;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getBiuldTime() {
        return biuldTime;
    }

    public void setBiuldTime(String biuldTime) {
        this.biuldTime = biuldTime;
    }
}

Plik src/main/java/pl/estrix/javafx/JavaApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package pl.estrix.javafx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@SpringBootApplication
@ComponentScan("pl.estrix.javafx")
@PropertySource("classpath:filter.properties")
@Configuration
public class JavaApplication extends Application {

    private ConfigurableApplicationContext context;
    private Parent rootNode;

    public static void main(final String[] args) {
        Application.launch(args);
    }

    @Override
    public void init() throws Exception {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(JavaApplication.class);
        context = builder.run(getParameters().getRaw().toArray(new String[0]));

        FXMLLoader loader = new FXMLLoader(getClass().getResource("/main.fxml"));
        loader.setControllerFactory(context::getBean);
        rootNode = loader.load();

    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(rootNode, 480, 320));
        primaryStage.centerOnScreen();
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        context.close();
    }

}

Plik src/main/java/pl/estrix/javafx/MainController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package pl.estrix.javafx;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MainController {

    @Autowired
    private FilterProp filterProp;

    @FXML
    private Label propName;
    @FXML
    private Label propVersion;
    @FXML
    private Label propBuildTime;

    @FXML
    public void initialize() {
        propName.setText(filterProp.getName());
        propVersion.setText(filterProp.getVersion());
        propBuildTime.setText(filterProp.getBiuldTime());
    }
}

Plik src/main/resources/main.fxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.Cursor?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="320.0" prefWidth="480.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.estrix.javafx.MainController">
   <children>
      <HBox alignment="CENTER" style="-fx-background-color: #ffffff;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
         <children>
            <AnchorPane prefHeight="320.0" prefWidth="255.0" HBox.hgrow="ALWAYS">
               <children>
                  <ImageView fitHeight="320.0" fitWidth="255.0" pickOnBounds="true" preserveRatio="true" AnchorPane.leftAnchor="0.0">
                     <image>
                        <Image url="@logo.png" />
                     </image>
                  </ImageView>
               </children>
            </AnchorPane>
            <VBox prefHeight="320.0" prefWidth="217.0" style="-fx-background-color: #ffffff;" HBox.hgrow="ALWAYS">
               <children>
                  <Label alignment="CENTER" contentDisplay="CENTER" maxWidth="1.7976931348623157E308" text="e-Strix.com" textAlignment="CENTER">
                     <VBox.margin>
                        <Insets bottom="10.0" top="10.0" />
                     </VBox.margin>
                  </Label>
                  <GridPane>
                    <columnConstraints>
                      <ColumnConstraints hgrow="SOMETIMES" maxWidth="106.0" minWidth="10.0" prefWidth="58.0" />
                      <ColumnConstraints hgrow="SOMETIMES" maxWidth="163.0" minWidth="10.0" prefWidth="163.0" />
                    </columnConstraints>
                    <rowConstraints>
                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                      <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
                    </rowConstraints>
                     <children>
                        <Label text="Name" />
                        <Label text="Version" GridPane.rowIndex="1" />
                        <Label text="Buil Time" GridPane.rowIndex="2" />
                        <Label fx:id="propName" text="Label" GridPane.columnIndex="1" />
                        <Label fx:id="propVersion" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="1" />
                        <Label fx:id="propBuildTime" text="Label" GridPane.columnIndex="1" GridPane.rowIndex="2" />
                     </children>
                  </GridPane>
               </children>
            </VBox>
         </children>
         <cursor>
            <Cursor fx:constant="SW_RESIZE" />
         </cursor>
      </HBox>
   </children>
</AnchorPane>

Plik src/main/resources/META-INF/spring.factories

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
pl.estrix.javafx.JavaApplication

Plik src/main/resources/logo.png

1
wget http://e-strix.com/wp-content/uploads/2018/04/logo.png -P src/main/resources/

Budowa lokalna

Uruchamianie

1
gradle clean generateMyResources bootRun

Budowanie jar

1
gradle clean generateMyResources bootJar

Uruchamianie

1
java -jar build\libs\JavaFxGitSample-1.0.1-SNAPSHOT.jar

No dobra, teraz wiemy jak zbudować aplikację. Ale docelowo ma to robić Ci, zatem zabieramy się za konfigurację job’ów.

Konfiguracja Jenkins

Budowa zdalna

Jenkins new project

W polu: Repository URL

git@git.e-strix.com:repositories/javafx-git-sample.git

W polu: Repository browser

gitlist

W polu: Repository browser -> URL

git.e-strix.com

W polu: Budowanie -> Invoke Gradle -> Version

Gradle_4_6

W polu: Budowanie -> Tasks

clean generateMyResources bootJar

 

Podnoszenie wersji

Kroki takie jak w przypadku „Budowa zdalna” z tą różnicą że w:

W polu: Budowanie -> Tasks

clean release --type minor

Gdzie wartość –type przyjmuje wartości:

  • major
  • minor
  • bugfix – domyślny

Publikacja na Landing Page

W polu: Budowanie -> Execute Shell -> Command

mkdir -p release_lib

rm -f ./release_lib/*

cp ${WORKSPACE}/../javafx-git-sample-release/build/libs/*.jar ./release_lib/

rm -f /home/appuser/public_html/download/*.jar

cp ./release_lib/*.jar /home/appuser/public_html/download/

 

Zakończenie

Jak widzisz storzenie projektu jest dość proste. Mimo że musiałem kilka godzin spędzić na odnalezieniu odpowiedniej dla mojego projektu ścieżki rozwoju. Przetestowałem kilka rozwiązań, pluginów i stwierdziłem że jednak najlepsze rozwiązanie to będzie ręczne napisanie skryptu (mimo że zapożyczyłem rozwiązanie od mądrych ludzi z internetu).

Landing Page dla naszego projektu

Wstęp

Dziś zajmiemy się promocją naszego przyszłego projektu. Do tego najlepiej wykorzystać coś prostego, no dobra ja jestem z tych co nie lubią się męczyć ze stroną internetową. W sumie to narzędzie nie powinno sprawiać problemów, nie ma się co szarpać jeśli nie mamy ludzi od promocji.

Zatem weźmy się do roboty, strona produktu…

I jeszcze raz od początku, zacznijmy od serwera Apache.

Apache + PHP

Już to przerabialiśmy mnóstwo razy, ale i tak zrobimy sobie jeszcze raz, ale trochę inaczej. Zatem do dzieła. Aktualizujemy repozytoria:

apt-get install python-software-properties software-properties-common
LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
apt-get update

i dalej serwer:

apt-get install apache2 php7.2 php7.2-fpm libapache2-mod-php7.2

I konfigurujemy. Wchodzimy do katalogu stron:

cd /etc/apache2/sites-available/

I tworzymy naszą konfigurację:

vim app.e-strix.com.conf

dalej, uzupełniamy zawartość pliku:

<VirtualHost *:80>
 ServerAdmin admin@e-strix.pl
 ServerName app.e-strix.com
 DocumentRoot /var/www/app.e-strix.com/public_html

 <Directory "/var/www/app.e-strix.com/public_html/">
   AllowOverride All
   Order allow,deny
   Allow from all
  </Directory>


 ErrorLog /var/www/app.e-strix.com/error.log
 CustomLog /var/www/app.e-strix.com/access.log combined
</VirtualHost>

Dodajemy katalog:

mkdir -p /var/www/app.e-strix.com

Teraz dodamy użytkownika i dodamy do grupy www-data:

adduser appuser
usermod -a -G appuser www-data

Tworzymy katalog publc_html w katalohu użytkowanika:

mkdir -p /home/appuser/public_html/

Teraz zrobimy coś nowego, czyli link symboliczny:

ln -s /home/appuser/public_html /var/www/app.e-strix.com

Po co nam to? O tym później…

A teraz niespodzianka, coś nowego. Postawimy sobie serwer FTP’a.

FTP

Po co on nam jest? A no po to by WordPress sam sobie dociągał pluginy czy też aktualizacje. Na początku wspominałem o chęci pozbycia się problemów a to rozwiązanie daruje nam wiele prac administracyjnych.

Teraz instalujemy na VPS’ie demona:

apt-get install vsftpd

Teraz zróbmy kopię zapasową oryginalnego pliku z konfiguracją:

mv /etc/vsftpd.conf /etc/vsftpd.conf.orig

I teraz potrzebujemy własnej konfiguracji, czyli tworzymy nasz plik:

vim /etc/vsftpd.conf

Z zawartością:

listen=YES
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
chroot_local_user=YES
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
allow_writeable_chroot=YES
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=50000
seccomp_sandbox=NO
user_sub_token=$USER
local_root=/home/$USER/public_html
userlist_enable=YES
userlist_file=/etc/vsftpd.userlist
userlist_deny=NO

Notatka: Zwróć uwagę że kopiowanie całości ustawień doda specyficzny znak końca linii, i wtedy serwer może się nie uruchomić.

Teraz zaczyna się już wszystko układać w całość.

Dodajmy teraz użytkownika do listy vsftpd.userlist.

echo "appuser" | tee -a /etc/vsftpd.userlist

I restartujemy naszego daemona:

service vsftpd restart

 I tu druga ciekawostka, czyli baza danych.

MySQL

No dobra, zajmijmy się dodaniem bazki. Tutaj też coś mało problemowego. Trzymam się firmy Oracle z większością produktów, dlatego małe projekty stawiam na bazach MySQL. Możesz się sprzeczać o to że są lepsze, że np. Postgresql jest o niebo lepsze, że bla bla bla. Uwierz mi, że źle utworzone zapytanie zarżnie każdą bazę, lub też przechowywanie zbyt dużej ilości danych archiwalnych przymuli każdy filesystem, a nam chodzi tu o prostotę i dostępność.

Zaczynamy oczywiście od instalacji:

apt-get install mysql-server php7.2-mysql

W trakcie tego procecsu, musimy mieć ustalone hasło do root. I w zasadzie tyle. Po zakończonym procesie instalacji możemy utworzyć bazę dla naszego CMS’a i użytkownika z którego będzie się ten CMS łączył do bazki.

Notatka: Nie łączymy się do bazy za pośrednictwem użytkownika root! W przyszłości pokażę dlaczego.

A zatem wchodzimy do MySQL’a z użytkownika root:

mysql -u root -p

I po wpisaniu hasła przystępujemy do tworzenia naszej bazy:

CREATE DATABASE app_e_strix_com;

Mamy bazę, więc dodamy użytkownika:

CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'password';

Nazwa użytkownika bazy jest zbieżna z nazwą użytkownika systemy, ale nie jest zależna. Mamy dowolność w dodawaniu nazw oraz haseł 😉

Teraz nadamy prawa do bazy. Nie chcemy aby żaden problemik pozamiatał nam tabele więc nie damy pełnych praw:

GRANT CREATE,DELETE,INSERT,SELECT,UPDATE ON app_e_strix_com.* TO appuser@localhost;

Notatka: Delete jest dyskusyjne, możemy w prawdzie ustawić trigger, aby nam przenosił dane w inne miejsce ale w tej chwili wykracza to poza potrzeby prezentacji.

Teraz już mamy strukturę naszych katalogów, więc spokojnie możemy dodać naszą konfigurację Apache, a zatem:

a2ensite app.e-strix.com

I przeładowanie:

service apache2 reload

Notatka: Dlaczego teraz a nie wcześniej? Odpowiedź jest prosta, po instalacji MySQL trzeba przeładować apache, aby nowo zainstalowane moduły były widoczne. W prawdzie można przeładować kilka razy, nie zaszkodzi, ale stwierdziłem że wykonam to właśnie w taki sposób.

A teraz nasz CMS, czyli instalacja WordPress’a.

WordPress

W prawdzie można użyć hostingu z już zainstalowanym CMSem, ale takie rozwiązanie jest mało elastyczne. Mam na myśli instalacje innych rzeczy niż tylko jednego narzędzia.

Przejdźmy do katalogu użytkownika:

cd /home/appuser/

I pobieramy WordPress:

wget https://wordpress.org/latest.tar.gz

A teraz rozpakujemy paczkę:

tar zxvf latest.tar.gz

I zawartość przenosimy do katalogu public_html:

mv ./wordpress/* ./public_html/

I czyścimy niepotrzebne pliki:

 rm -r latest.tar.gz wordpress/

I nadajemy prawa do katalogu:

chown -R appuser:www-data public_html/
chmod -R 774 public_html/

Z poziomy administracji już wszystko gra, teraz użyjemy przeglądarki do finalizacji procesu instalacji:

A teraz instalacja numer 2, czyli już coś ładnie wygląda.

1. Wpisujemy dane naszej bazki

Teraz wypełnijmy formularz danymi, które ustaliliśmy przy tworzeniu bazy MySQL. I przechodzimy dalej.

 

2. Ustalamy konto administratora

Warto je sobie zapisać, żeby nie szukać w przyszłości. Mam na myśli sytuację, gdy będzie potrzeba coś zmienić, lub dodać osobę zajmującą się stroną. Gdy nasza aplikacja nabierze rozgłosu, może wystąpić sytuacja że nie będzie możliwości ogarnięcia wszystkiego w pojedynkę.

Klikamy przycisk „Let’s go!”.

Wypełniamy formularz i przechodzimy dalej:

Zakończenie

No i teraz mamy wszystko. Pozostaje nam wybrać sobie ładny layout i theme. Podłączyć statystyki googlowskie, lub coś innego np Piwik’a, lub zupełnie coś innego… Potem integracja z AdSense, może strona na początku na siebie zarobi, i integracja z portalem społecznościowym, ja wybiorę facebook. Ale o tym w przyszłości. Na chwilę obecną mamy domyślny motyw i to w rezultacie jest dobrym fundamentem do pracy.

Ciągła integracja

Wstęp

Normalnie żaden projekt informatyczny nie jest budowany przez wydelegowaną osobę, nie miało by to większego sensu, ale chętnie na taki etat bym aplikował. No ale niestety dzisiejsze system ciągłej integracji zabierają naprawdę fajną pracę.

Tak czy inaczej dziś przedstawiam jak uruchomić Jenkins’a, jako że opensource ma się dobrze to wybieram tę wersję. Oczywiście nie narzucam wybieranie tego konkretnego rozwiązania.  Równie dobrze możesz wybrać Travis’a, TeamCity, Hudson’a czy którykolwiek z systemów jaki uważasz. Ja wybieram Jenkins’a ponieważ każda firma z którą pracowałem miała go na pokładzie.

Zatem zacznijmy od początku, czyli Java:

  1. Java

    Pierwszym krokiem będzie dodanie repozytorium do naszego Ubuntu i pobranie z niego zawartości:

    add-apt-repository ppa:webupd8team/java
    apt-get update

    Teraz spokojnie możemy instalujemy:

    apt-get install oracle-java8-installer

    Po całym procesie nasza java powinna wskazywać poprawną wartość, czytaj 1.8.x. Zatem zapytajmy się jaką wersje właśnie zainstalowaliśmy:

    java -version

    i/lub kompilator:

    javac -version

    Dla pewności, możemy upewnić się że nie będzie zgrzytów na etapie zależności. Więc sprawdzamy alternatywy:

    update-alternatives --config java

    Wybieramy naszą instalację i wszystko teraz będzie działać. W kolejnym kroku dodamy naszą instalację do zmiennych środowiskowych:

    vim /etc/environment

    Dodaj na początku klucz=wartość na początku pliku:

    JAVA_HOME="/usr/lib/jvm/java-8-oracle"

    I aktualizujemy zmiany poprzez komendę source:

    source /etc/environment 
    

    Teraz już jest definitywny koniec działań z javą. Sprawdzamy czy działa zmienna środowiskowa:

    echo $JAVA_HOME

    Działa, więc zainstalujmy robotników od czarnej roboty:

  2. Maven

    Maven, solidny framework oparty na pluginach, buduje aplikacje dla banków, ubezpieczeń, portale i inne bardzo ważne rzeczy.
    Zatem pobieramy i rozpakowujemy:

    Notatka: Tę akcję wykonuje w katalogu „/tmp”, ale nie ma takie obowiązku. Pamiętaj że jesteś na prawach „root”, więc to zobowiązuje do czegoś.

    wget http://ftp.man.poznan.pl/apache/maven/maven-3/3.5.3/binaries/apache-maven-3.5.3-bin.tar.gz
    
    tar zxvf apache-maven-3.5.3-bin.tar.gz

    Teraz przenieśmy do katalogu /opt/:

    mv apache-maven-3.5.3/ /opt/

    I dodajemy zmienne środowiskowe, podobnie jak miało to miejsce dla pkt.1.
    Otwieramy plik środowisko:

     vim /etc/environment

    i dodajemy naszą zmienną:

     M2_HOME="/opt/apache-maven-3.5.3"

    Teraz jeszcze na końcu zmiennej PATH należy dodać odwołanie do katalogu bin:

    :$M2_HOME/bin

    Teraz aktualizujemy ponownie środowisko:

    source /etc/environment

    i sprawdzamy co się wydarzyło:

    # mvn -v 
    Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-24T20:49:05+01:00) 
    Maven home: /opt/apache-maven-3.5.3 
    Java version: 1.8.0_161, vendor: Oracle Corporation Java home: /usr/lib/jvm/java-8-oracle/jre 
    Default locale: en_US, platform encoding: UTF-8 
    OS name: "linux", version: "4.4.0-92-generic", arch: "amd64", family: "unix"
  3. Gradle

    Kolejnym specjalistom do spraw budowy jest Gradle, projekt bardzo szybko się rozwija i niesie ze sobą duży potencjał. Pierwsza różnica jaka jest widoczna w stosunku do Mavena, to plik ustawień bazuje na formacie skryptów Groove, gdy poprzednika opisywaliśmy w XMLach.

    A zatem do dzieła. Pobieramy i rozpakowujemy:

    wget https://services.gradle.org/distributions/gradle-4.6-bin.zip
    unzip gradle-4.6-bin.zip

    Teraz przeniesiemy do katalogu /opt/, tak jak poprzednika:

    mv gradle-4.6/ /opt/

    I zmienne środowiskowe:

    vim /etc/environment

    Dodajemy wpis:

    GRADLE_HOME="/opt/gradle-4.6"

    i do zmiennej PATH, dodajemy odwołanie do katalogu bin:

    $GRADLE_HOME/bin

    Teraz aktualizacja środowiska:

    source /etc/environment

    i sprawdzamy efekt:

    # gradle -v 
    ------------------------------------------------------------ 
    Gradle 4.6 
    ------------------------------------------------------------ 
    Build time: 2018-02-28 13:36:36 UTC 
    Revision: 8fa6ce7945b640e6168488e4417f9bb96e4ab46c 
    Groovy: 2.4.12 
    Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017 
    JVM: 1.8.0_161 (Oracle Corporation 25.161-b12) 
    OS: Linux 4.4.0-92-generic amd64
  4. Jenkins

    Całkiem spoko, jak do tej pory. Teraz czas na gwiazdę odcinka!

    Dodajemy repozytorium do nasze list w Ubuntu i aktualizujemy jak w przypadku Java’y:

    echo "deb https://pkg.jenkins.io/debian binary/" >> /etc/apt/sources.list
    apt-get update

    Teraz pora na główne zaklęcie:

    apt-get install jenkins

    Notatka: Instalator zapyta nas czy zainstalować aplikację oraz o to czy ma instalować bez sprawdzania poprawności. Ja należę do tych, którzy wierzą w oficjalną wersję zamieszczoną na oficjalnych stronach. W sumie ktoś by zauważył, gdyby coś niezdrowego się działo.

    Po instalacji może sprawdzić czy nasz serwis działa. Wykonaj komendę:

    service jenkins status

    Teraz czas na konfigurację:

    Przejdźmy na stronę:

    jenkins.e-strix.com:8080
    

    Widzimy że aplikacja działa. Super!
    Teraz mamy do wpisania klucz, o który prosi nas pierwszy formularz. Pobierzmy go zgodnie z instrukcją:

    cat /var/lib/jenkins/secrets/initialAdminPassword

    Teraz wybierzmy sobie opcje, które pluginy mają być dodane przy instalacji. Ja wybieram wersję domyślną. Do naszej pracy wystarczy ta opcja, ale o tym będzie dalej.

    Teraz sobie chwilę poczekajmy aż instalacja przejdzie do końca i pojawi się formularz w którym będziemy poproszeni o utworzenie pierwszego administratora.

    W sumie działa, zatem możemy na tym zakończyć… Ale kole mnie po oczach ten port w pasku przeglądarki. Zatem zróbmy sobie ProxyPass. O tym w kolejnej części o serwerze Apache.

     

  5. Apache

    W zasadzie możemy pominąć kilka pierwszysch kroków instalacji ponieważ poruszałem ten temat już w dwóch wpisach, na temat Subversion/SVN oraz o Git’cie. Ale przypomnę w dość szybki sposób.

    A zatem instalujemy:

    apt-get install apache2

    Włączamy potrzebne moduły:

    a2enmod rewrite
    a2enmod proxy
    a2enmod proxy_http
    a2enmod headers
    a2enmod proxy_html
    a2enmod xml2enc

    i restartujemy serwer Apache:

    service apache2 restart

    Ok, teraz przejdźmy do katalogu dostępnych stron:

    cd /etc/apache2/sites-available/

    Tworzymy katalog naszej strony:

    mkdir -p /var/www/jenkins.e-strix.com/public_html/

    i dalej konfiguracja naszej strony:

    vim jenkins.e-strix.com.conf

    i jej zawartość

    <VirtualHost *:80>
    
     ServerAdmin k.mucik@e-strix.pl
     DocumentRoot /var/www/jenkins.e-strx.com/public_html
    
     <Directory "/var/www/jenkins.e-strx.com/public_html/" >
      Options Indexes FollowSymLinks MultiViews
      AllowOverride All
      allow from all
      Require all granted
    
      DirectoryIndex index.html
     </Directory>
    
     ServerName jenkins.e-strix.com
    
     ProxyRequests Off
     AllowEncodedSlashes NoDecode
    
     ProxyPass / http://127.0.0.1:8080/ nocanon
     ProxyPassReverse / http://127.0.0.1:8080/
     ProxyPassReverse / http://jenkins.e-strix.com/
    
     ErrorLog /var/www/jenkins.e-strix.com/error.log
     CustomLog /var/www/jenkins.e-strix.com/access.log combined
    
    </VirtualHost>

    I gra gitara… Teraz dodajemy naszą stronę i przeładowywujemy ostawienia:

    a2ensite jenkins.e-strix.com
    service apache2 reload

    Podsumowanie

    Teraz mamy już prawie wszystko aby móc automatycznie budować i publikować projekty.

    Prawie? Czyli co jeszcze?

Odpowiedź jest dość prosta, brakuje nam projektów oraz miejsca gdzie będzie publikacja. O tych elementach będą kolejne wpisy. Projekty oczywiście będą korzystały z naszej dokonanej pracy.

svn + WebSVN

Wstęp

Każdy etap prowadzenia projektu wiąże się nierozerwalnie z posiadaniem kodu oraz dokumentacji do projektu. Każdy programista wie jak ważny jest czas poświęcony na tworzenie algorytmów i ich implementacja. Jeśli jesteś osobą, która nie doświadczyła jeszcze utraty kodu poprzez błąd systemu lub dysku, spokojnie możesz zignorować poniższy tutorial, w innym przypadku zapraszam do zapoznania się z instrukcją jak postawić własny serwer kontroli wersji za złotówkę miesięcznie.

Oczywiście zapytasz czy słyszałem o dobrodziejstwie takim jak GitHub czy inny serwer. Tak, coś mi się obiło przypadkiem o uszy… Problem jest taki że darmowe konta są publiczne a prywatne kosztuje dziś 7 dolarów amerykańskich. Za tę kwotę można spokojnie wykupić sobie  serwer prywatny (trochę więcej niż wersję podstawową) i samodzielnie postawić odpowiednie narzędzia. Nie będę opisywał jak korzystać z Subversion, jest tego pełno w internecie…

W tym artykule poruszę konfigurację Subversion, opowiem o tym jak zainstalować go na serwerze z systemem Ubuntu, skonfigurować odpowiednie pliki oraz ścieżki. W dalszej części pokażę jak zainstalować klienta webowego WebSVN do przyjemnego podglądania zmian w repozytorium. Jeśli jesteś fanem gita, zachęcam do zobaczenia artykułu o instalacji gita wraz z klientem webowym GitList tutaj.

 

L.P. Czego potrzebujemy
1 VPS
2 System Operacyjny ja wybrałem Ubuntu 16.04
3 Dostęp po SSH z prawami root
4 http://websvn.tigris.org/files/documents/1380/49057/websvn-2.3.3.zip
5 Skonfigurowana domena na serwer VPS

Instalujemy PHP

Serwer który ja wybrałem oferuje instancję Ubuntu 16.04, która posiada w sobie zainstalowaną wersję PHP 7.0, jest ona wystarczalna dla naszych potrzeb. Jeśli zdecydujesz się na instalację wyższej wersji lub niższej, też jest ok. Wykorzystywane narzędzia wymagają PHP 5, zatem bez wahania decyduj.

apt-get install php7.0 php7.0-fpm -y

Instalujemy Apache

Kolejny krok to instalcja serwera apache, może również wykorzystać nginx. Ja korzystam z Apache chyba przez zasiedzenie. Jest on dość prymitywny ale działa i jest łatwy do konfigurowania. Apache spokojnie może obsługiwać do 250 użytkowników jednocześnie więc dla niewielkich stron można śmiało przy nim zostać, jeśli przewidujemy ruch do 1000 użytkowników jednocześnie zainteresujmy się serwerem nginx lub stworzeniem farmy serwerów.

Wykonajmy polecenie:

apt-get install subversion apache2 libapache2-svn libapache2-mod-php php-xml-parser

Ok, teraz mamy zainstalowany serwer i czas na konfigurację. Przejdźmy do katalogu dostępnych stron w serwerze i stwórzy nasz plik konfiguracyjny:

cd /etc/apache2/sites-available/
vim svn.e-strix.com.conf

Ja używam Vim’a do edycji, ale użyj dowolnie. Początki z edytorami textowymi są trudne ale warte nauki.

Plik wypełnij zawartością wg nw. listingu:

<VirtualHost *:80> 
ServerAdmin kontakt@e-strix.pl 
ServerName svn.e-strix.com 

DocumentRoot /var/www/svn.e-strix.com/public_html/ 

<Directory "/var/www/svn.e-strix.com/public_html/"> 
DirectoryIndex index.php index.html 
Options FollowSymLinks 
AllowOverride All 
</Directory> 

<Location /repo> 
DAV svn 
SVNParentPath /home/svnuser/repo 
SVNListParentPath On 
</Location> 

ErrorLog /var/www/svn.e-strix.com/error.log 
CustomLog /var/www/svn.e-strix.com/access.log combined 
</VirtualHost>

Stwórz katalog w którym będą nasze aplikacje:

mkdir -p /var/www/svn.e-strix.com/

ustaw stronę jako aktywną i przeładuj ustawienia Apache:

a2ensite svn.e-strix.com
service apache2 reload

Tworzymy repozytorium

Repozytorium subversion podobnie jak git’a może być gdziekolwiek na dysku. Dla zachowania przejrzystości, utworzymy konto użytkownika svn a w nim katalog z naszymi projektami.

Zatem dodajemy użytkownika i uzupełniamy dane o które poprosi system:

adduser svnuser
usermod -a -G svnuser www-data

Dalej tworzy nasz katalog repozytorium:

mkdir -p /home/svnuser/repo/

Gdy poprawnie zainstalujemy pakiet subversion, dostępne będzie dla nas narzędzie svnadmin do zarządzania projektami. Korzystamy z niego dodając nowe repozytorium:

svnadmin create /home/svnuser/repo/project1

Przejdźmy teraz do innego katalogu, ja wybrałem katalog /tmp i pobierzmy kopię repozytorium. W tym przypadku posłużę się kopiowaniem z pliku, komenda co jest skrótem od checkout. A zatem:

cd /tmp/
svn co file:///home/svnuser/repo/project1

Subversion pobrał na projekt, wejdźmy do środka i stwórzmy dowolny plik. Ja wybrałem .svnignore, może być dowolny:

cd project1/
touch .svnignore

Teraz dodajmy go używając komendy add oraz wypchnijmy zmiany do naszego repozytorium ci -m „comment”, bardzo ważne jest używanie przełącznika -m:

svn add .svnignore
svn ci -m "init commit"

Dodajemy klienta webowego

Jest wiele dostępnych na rynku klientów webowych, każdy z nich działa. Nie będę się rozdrabniał na to, który jest mikrosekundę szybszy czy ma lepiej napisany kod. To jest dla nas nie istotne bo i tak w większości będziemy pracować na lokalnych maszynach w wybranym IDE lub konsoli. WebSVN u mnie wygrał ze względu na intuicyjność i funkcjonalność. Ma też przyjemny interfejs, który mnie nie denerwuje więc dobrze mi się z nim pracuje.

W pierwszej kolejności musimy nadać prawa do katalogu repozytorium dla użytkownika www-data, który jest wykorzystywany przez serwer www.

Notatka: Nie jest to część poświęcona idealnemu skonfigurowaniu katalogów. Jeśli masz życzenie tworzyć hierarchię grup i użytkowników to śmiało.

Zatem zmieniamy właściciela katalogu:

cd /home/svnuser
chown -R svnuser:www-data repo/

Przejdźmy teraz do naszego katalogu aplikacji i pobierzmy klienta:

Notatka: wymagany jest program wget.

cd /var/www/svn.e-strix.com/
wget http://websvn.tigris.org/files/documents/1380/49057/websvn-2.3.3.zip

Teraz rozpakujmy pobrane archiwum, zmieńmy nazwę wypakowanego katalogu websvn-2.3.3 na public_html, zgodny z plikiem konfiguracyjnym serwera:

Notatka: Wymagany jest program unzip. Może nie być zainstalowany domyślnie.

unzip websvn-2.3.3.zip
mv websvn-2.3.3/ public_html/

Wyczyścimy śmieci i nadamy właściciela plików:

rm websvn-2.3.3.zip

chown -R www-data:www-data public_html/

Konfiguracja klienta

Teraz już z górki. Do pełnego sukcesu potrzebujemy jeszcze skonfigurować naszego klienta z utworzonym repozytorium.

Przechodzimy do katalogu public_html/include i w nim musimy utowrzyć plik konfiguracyjny. Całe szczęście że dostawca załączył przykładowy plik distconfig.php, który możemy wykorzystać… Utworzymy plik w tej lokalizacji o nazwie config.php.

cd public_html/include
vim config.php

Jego zawartość znajduje się na nw. listingu. Więc przekopiuj i zapisz plik:

<?php
$config->parentPath('/home/svnuser/repo');
$config->addTemplatePath($locwebsvnreal.'/templates/calm/','group');
?>

Teraz spokojnie możemy wpisać adres w przeglądarce i naszym oczom pojawi się zawartość naszego jednego repozytorium. Ale co w przypadku gdy bym miał ich więcej? Spokojnie… w przypadku gdy jest tylko jedno repo aplikacja od razu przekierowuje na jej zawartość, jeśli będzie więcej projektów, pierw ukaże się nam ich lista.

Dodajmy na serwerze kolejny projekt:

svnadmin create /home/svnuser/repo/project2
chown -R svnuser:www-data /home/svnuser/repo/project2/
chmod -R 774 /home/svnuser/repo/project2/

I teraz spróbujemy skorzystać z innego komputera, w taki sposób aby ruch odbył się przez sieć. Ja użyłem swojego laptopa.

Przechodzimy na dowolny katalog z prawami zapisu.

Teraz pobieramy nasze repozytorium:

svn co http://svn.e-strix.com/repo/project2/

Dodamy jakiś plik do projektu i wypchniemy zmiany na serwer:

cd project2/
vim index.html
svn add index.html
svn ci -m "init commit"

Zmiany możemy obserwować już na serwerze.

Podsumowanie

Systemy kontroli wersji są podstawą przy pracy programisty czy DevOps’a, szukanie błędów w kodzie czy winnych jest dużo łatwiejsze gdy można śledzić każdą zmianę. Powstaje także odpowiedzialność osoby za swoje „dzieło”. Kolejnym plusem okazuje się możliwość zdalnego budowania poprzez odpowiednie narzędzia CI takie jak Hudsom, Jenkins, Travis czy TeamCity, które można ustawić w taki sposób aby czasowo budowały aplikację czy dla każdego komitu. Tak rozproszony system bardzo przyspiesza pracę nad projektem i pozwala deweloperowi skupić się nad konkretnym zadaniem.

Co wybrać? Czy Subversion czy Git? Ja używam obu w pracy, dla nowych projektów zainteresowałbym się gitem ale i svn też jest bardzo popularny. Pamiętajcie że duże instytucje nie lubią zmian…

git + GitList

 Wstęp

Każdy etap prowadzenia projektu wiąże się nierozerwalnie z posiadaniem kodu oraz dokumentacji do projektu. Każdy programista wie jak ważny jest czas poświęcony na tworzenie algorytmów i ich implementacja. Jeśli jesteś osobą, która nie doświadczyła jeszcze utraty kodu poprzez błąd systemu lub dysku, spokojnie możesz zignorować poniższy tutorial, w innym przypadku zapraszam do zapoznania się z instrukcją jak postawić własny serwer kontroli wersji za złotówkę miesięcznie.

Oczywiście zapytasz czy słyszałem o dobrodziejstwie takim jak GitHub czy inny serwer. Tak, coś mi się obiło przypadkiem o uszy… Problem jest taki że darmowe konta są publiczne a prywatne kosztuje dziś 7 dolarów amerykańskich. Za tę kwotę można spokojnie wykupić sobie  serwer prywatny (trochę więcej niż wersję podstawową) i samodzielnie postawić odpowiednie narzędzia. Nie będę opisywał jak korzystać z Git’a, jest tego pełno w internecie…

W tym artykule poruszę konfigurację systemu Git, opowiem o tym jak zainstalować go na serwerze z systemem Ubuntu, skonfigurować odpowiednie pliki oraz ścieżki. W dalszej części pokażę jak zainstalować klienta webowego GitList do przyjemnego podglądania zmian w repozytorium. Jeśli jesteś fanem Subversion, zachęcam do zobaczenia artykułu o instalacji svn’a wraz z klientem webowym WebSVN tutaj.

L.P. Czego potrzebujemy
1 VPS
2 System Operacyjny ja wybrałem Ubuntu 16.04
3 Dostęp po SSH z prawami root
4 https://github.com/klaussilveira/gitlist/releases
https://s3.amazonaws.com/gitlist/gitlist-0.3.tar.gz
5 Skonfigurowana domena na serwer VPS

1. Instalujemy PHP7.2 na Ubuntu 16.04

Gdy mamy zainstalowany system Ubuntu 16.04 w wersji serwerowej, mamy już zainstalowany PHP w wersji 7.0, dla naszego artykułu nie potrzebujemy wyższej wersji (w sumie wersja 5 jest równie dobra) ale pokażę jak podbić wersję do PHP 7.2.

Dodajmy pierw odpowiednie repozytoria do czystej instancji VPSa:

apt-get install python-software-properties software-properties-common 
LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php 
apt-get update

Teraz usuńmy poprzednie (jeśli istnieją) instalacje:


1
apt-get remove php5-common -y

lub opcja purge, usuwanie z konfiguracjami:


1
apt-get purge php5-common -y

Teraz instalacja cora PHP7:

apt-get install php7.2 php7.2-fpm php7.2-xml -y

i czyścimy śmieci:


1
apt-get --purge autoremove -y

Wynik jaki uzyskaliśmy:

# php -v
PHP 7.2.3-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Mar 6 2018 11:18:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
 with Zend OPcache v7.2.3-1+ubuntu16.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies

2. Instalujemy GIT’a

Git jest dziś dostarczany do niemal każdej dystrybucji systemu operacyjnego. Jeśli z jakiegoś przypadku nie masz zainstalowanego git’a, oto komenda naprawiająca ten błąd.

apt-get install git

W wyniku oczywiście otrzymamy najnowszą wersję uznaną przez prowajdera:

# git --version
git version 2.7.4

3. Instalujemy Apache2

Kolejny krok to instalacja serwera, który będzie obsługiwał klienta webowego. Wybieram Apache ze względu na własne zasiedzenie przy nim. Nie ma też problemu z wykorzystaniem konkurencyjnych produktów.

apt-get install apache2 libapache2-mod-php

Dodajemy jeszcze moduł rewrite, aby pozbyć się niepotrzebnych nazw skryptów z url’a. Po tym musimy zrestartować serwer. Nie wystarczy sam reload, to jest za poważna zmiana ustawień.

a2enmod rewrite
service apache2 restart

Wynik:

Zainstalowany Apache2

 

4. Dodajemy stronę git.e-strix.com

Teraz dodamy konfigurację naszej strony. Jest to konieczne aby zmienić wyświetlany domyślny katalog dla apache.

Notatka: Ważne jest aby dodać do naszych hostów adres IP i nazwę domeny. Dla mnie to będzie git.e-strix.com. Osobiście korzystam z hostingu i definicji rekordów A.

cd /etc/apache2/sites-available

i dalej tworzymy plik konfiguracyjny naszej strony:

vim git.e-strix.com.conf

Notatka: Możesz nie mieć zainstalowanego edytora tekstowego. Dla mojej pracy używam Vim’a, bo go lubię i znam. Nie ma powodów żeby wybierać tylko jego. Jeśli wolisz Nano czy masz zainstalowany program MidnightCommander z wewnętrznym edytorem, śmiało wybierz najlepszą dla siebie opcje.

Zawarość pliku:

<VirtualHost *:80>
        ServerAdmin kontakt@e-strix.pl
        ServerName git.e-strix.com
        DocumentRoot /var/www/git.e-strix.com/public_html/

        <Directory "/var/www/git.e-strix.com/public_html/">
                DirectoryIndex index.php index.html

                Options FollowSymLinks
                AllowOverride All
        </Directory>

        ErrorLog /var/www/git.e-strix.com/error.log
        CustomLog /var/www/git.e-strix.com/access.log combined

</VirtualHost>

Tworzymy katalog docelowy, gdzie będą przechowywane pliki aplikacji GitList:

mkdir -p /var/www/git.e-strix.com/public_html

i każemy apache udostępnić naszą stronę w sieci:

a2ensite git.e-strix.com.conf
service apache2 reload

Notatka: Pamiętaj o dodaniu przekierowania DNS na IP serwera na którym stawiasz serwer.

Efekt:

5. Instalujemy GitList

Aplikacja GitList jest stworzona w PHP i jest prosta w obsłudze, dlatego postanowiłem o niej napisać. Jako programista nie mam zamiaru poświęcać więcej czasu na administrację serwer niż jest to konieczne. Dlatego wybieram narzędzia oparte o PHP aby nie bawić się w budowanie, publikowanie i namiętne analizowanie dzienników.

Zatem podążając wg instrukcji:

  • Pobieramy GitList z gitlist.org i wypakowujemy do naszego katalogu /var/www/gitlist.e-strix.com/public_html/
  • Zmieniamy nazwę pliku config.ini-example na config.ini
  • Podmieniamy ścieżkę repozytorium
  • Tworzymy catalog cache z uprawnieniami czytanie/pisanie wg. instrukcji

Notatka: Ja wybrałem wersję 0.3 –  z powodu błędu jaki uzyskałem w nowszej wersji. W przyszłości spróbuję aktualizować aplikację, natomiast na chwilę obecną będę obserwował. Błędy wynikają niestety z wersji PHP7, dla wersji 7.2 build z 6 marca dalej nie pomógł.

cd /var/www/git.e-strix.com/public_html
wget https://s3.amazonaws.com/gitlist/gitlist-0.3.tar.gz

Rozpakujemy pobrane archiwum i zmienimy nazwę katalogu na public_html, jako ten z którego korzysta serwer. W dalszej kolejności należy nadać właściciela dla wszystkich plików.

Notatka: Jeśli korzystasz z użytkownika root do wykonania wszystkich czynność (zakładam że tak, i instalujesz na świeżej instancji serwera), to zmuszony jesteś nadać odpowiednie prawa do wszystkich katalogów i plików, lub najprościej zmienić właściciela plików. Domyślnym użytkownikiem, który obsługuje serwer apache jest www-data, i grupa www-data.

tar zxvf gitlist-0.3.tar.gz
rm gitlist-0.3.tar.gz
cd ..
chown -R www-data:www-data public_html/

W porządku teraz wg instrukcji musimy stworzyć katalog cache i nadać mu pełne prawa (sic!) w naszej aplikacji, więc wjedźmy do katalogu public_html i zróbmy to o co nas proszą:

cd public_html/
chmod 777 cache

Notatka: Katalog oczywiście się utworzy z właścicielem i grupą „root”, przy tych uprawnieniach nie mamy się co martwić brakiem uprawnień, ponieważ uprawnienia 777 są najwyższe jakie mogą być.

Teraz dodajmy konfigurację czyli zgodnie z ww. instrukcją, edytujemy plik config.ini i określamy gdzie będzie znajdowało się nasze repozytorium.

vim config.ini

Domyślnie jest ustawiona wartość „/home/git/repositories/„, myślę że spokojnie możemy wykorzystać ten katalog. Dla mojej pracy nie ma szczególnych wymagań co do tej struktury, a zatem do dzieła przejdźmy do dodania użytkownika git.

[git]
client = '/usr/bin/git' ; Your git executable path
repositories = '/home/git/repositories/' ; Path to your repositories

Dodajemy użytkownika i uzupełnimy potrzebne dane:

adduser git

Teraz należy dodać nasz katalog repozytorium:

mkdir -p /home/git/repositories/

Teraz wejdźmy do środka i dodajmy nasze pierwsze repozytorium:

cd /home/git/repositories/

git init --bare project1.git
chown -R git:git project1.git/

Czas sprawdzić czy nasze repozytorium działa i czy możemy się do niego dostać. Najlepiej sprawdzić to poprzez wykorzystanie innej maszyny, zatem przechodzimy na komputer lokalny lub inny dostępny i spróbujemy pobrać nasz projekt:

git clone git@git.e-strix.com:repositories/project1.git

No masz, działa…

Teraz wejdźmy do środka i zróbmy komit inicjalizacyjny:

cd project1/
touch .gitignore
git add .gitignore
git commit -m "init commit"
git push

Notatka: Pamiętaj o nadaniu odpowiednich uprawnień lub przypinanie użytkownika do odpowiedniej grupy w systemie.

 

W rezultacie otrzymamy wynik:

JavaFx: Kontrolki

Interfejs jest tworzony przez scalanie węzłów screena-wykresu w hierarchii wewnątrz sceny (Scene). Trzy typy Rodziców (Parent) mają różne cechy układu:

  • Grupy (Group): nie wykonuje żadnego pozycjonowania dzieci i nie jest bezpośrednio skalowalny (patrz następna sekcja) i po prostu przyjmuje zbiorowe granice swoich widocznych dzieci. Grupy są najbardziej odpowiednie, gdy trzeba statycznie zmontować kolekcję węzłów na ustalonych pozycjach i / lub zastosować efekt lub przekształcić go w tę kolekcję.
  • Region (Region): klasa podstawowa dla wszystkich paneli układu ogólnego przeznaczenia; jest skalowalny i można go wymieniać za pomocą CSS. Obsługuje dynamiczny układ poprzez zmianę rozmiaru i pozycjonowanie elementów podrzędnych podczas przejścia układu (w razie potrzeby). Jeśli podklasa Region (panel układu) obsługuje paradygmat układu, którego potrzebujesz, użyj go zamiast pozycjonowania ręcznego z Grupą.
  • Kontrolka (Control): klasa podstawowa dla wszystkich sterowań podlegających skróceniu. Jest resizable i podklasy są stylable przez CSS. Kontroluje przekazywanie układu do ich skórek (które są regionami). Każda podklasa Control layout zapewnia unikalny API do dodawania treści w odpowiednim miejscu w skórze; nie dodajecie bezpośrednio dzieci do kontrolki.

Prosty przykład, który daje przedsmak, jeśli nigdy nie programowałeś FX:

Text text = new Text("JavaFX 2.0!");
text.setFill(Color.RED);
text.setFont(new Font(24));
Line line = new Line(2, 8, 104, 8);
line.setStroke(Color.BLUE);
line.setStrokeWidth(4);
Group group = new Group();
group.setEffect(new DropShadow());
group.getChildren().addAll(text, line);

BorderPane root = new BorderPane();
root.setTop(new Label("Introducing..."));
root.setCenter(group);

Scene scene = new Scene(root);
stage.setScene(scene);
stage.setVisible(true);

Aby zapoznać się z podstawowymi pojęciami dotyczącymi układu wykresów scen, przyjrzyjmy się najważniejszym klasom:

Zmiana rozmiaru

Każdy Węzeł na wykresie sceny ma potencjał, by być „skalowalnym”, co oznacza, że wyraża jego minimalne, preferowane i maksymalne ograniczenia rozmiaru i umożliwia rodzicowi zmianę jego rozmiaru podczas układania (miejmy nadzieję, że w tym zakresie, jeśli rodzic jest honorowy) .

Każde okienko układu implementuje własne zasady dotyczące zmiany rozmiaru zmiennego rozmiaru dziecka, biorąc pod uwagę zakres wielkości dziecka. Na przykład, StackPane spróbuje zmienić rozmiar wszystkich dzieci, aby wypełnić swoją szerokość / wysokość (przestrzegając maksymalnych limitów), podczas gdy FlowPane zawsze zmienia rozmiar dzieci do ich preferowanych rozmiarów, co rozumiemy pod pojęciem „autosize” (zmiana rozmiaru na preferowane).

Ale oto co zaskakuje (i denerwuje) ludzi: nie wszystkie klasy węzłów grafu sceny są skalowalne:

  • Skalowalne:  Region, Control, WebView
  • Nieskalowalne: Group, Text, Shape, MediaView*, ImageView*

*zostanie zmieniony w przyszłym wydaniu

Jeśli klasa Node nie może zmienić rozmiaru, to isResizable() zwraca false, minWidth()/minHeight(), prefWidth()/prefHeight() i maxWidth()/maxHeight() wszystkie zwracają bieżący rozmiar, a resize() to no-op. Na wielkość nieodkształcalnego węzła wpływają jego różne właściwości (szerokość / wysokość dla prostokąta, promień dla okręgu, itd.) I obowiązkiem aplikacji jest ustawienie tych właściwości (i pamiętaj, że skalowanie NIE jest tym samym co zmiana rozmiaru, ponieważ Skala przekształci również obrys, co zwykle nie jest tym, czego potrzebujesz do układu). Gdy nieskalowalne węzły są rzucane do paneli układu, będą one ustawione, ale nie zostaną zmienione.

 

Wskazówka: jeśli potrzebujesz skalowalnego prostokąta, użyj regionu i nadaj mu styl CSS.

Odchylenie zawartości

W przypadku większości klas węzłów szerokość i wysokość są niezależne od siebie, jednak istnieje grupa, w której wysokość zależy od szerokości lub odwrotnie. Aby poprawnie poradzić sobie z tymi obiektami, układ potrzebuje sposobu na jego odszyfrowanie, stąd metoda węzła getContentBias(). Zwraca domyślnie wartość null, co oznacza brak zależności szerokości/wysokości, a kod układu może przepuszczać -1 jako alternatywny wymiaru do metod minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w) i maxWidth(h)/maxHeight(w).

Oznakowane elementy sterujące mają odchylenie w wartościach poziomych, gdy włączone jest zawijanie. FlowPane i TilePane obsługują odchylenie w stosunku do ich orientacji. Uwaga: okazuje się, że HBox, VBox i StackPane mają zerowy wpływ na treść, jednak powinny one monitorować odchylenie w oparciu o uprzedzenia docelowe i pomne: jest to błąd, który zostanie naprawiony przed ostateczną wersją.

Nasze klasy layoutów zajmują się podnoszeniem zawartości w systemie, więc dopóki nie napiszesz niestandardowej klasy układu, która obsługuje dowolne węzły, nie musisz zajmować się bezpośrednio tym interfejsem. Po prostu wiesz, że układ automatycznie obsługuje treść, gdy budujesz swoje interfejsy.

Przesłanianie Min / Pref / Max Sizes

Większość klas kontroli i paneli układu jest całkiem niezła w obliczaniu, jak duże powinny być oparte na ich zawartości i ustawieniach właściwości – nazwijmy to ich „wewnętrznymi” minimalnymi, preferowanymi i maksymalnymi rozmiarami. Na przykład Przycisk (Button) oblicza te rozmiary na podstawie ich tekstu, czcionki, wypełnienia itd .; dodatkowo domyślny maksymalny rozmiar przycisku jest ustawiony domyślnie, ponieważ zazwyczaj nie chcemy, aby przyciski rozszerzały się, aby wypełnić przestrzeń. Nie powinieneś wyraźnie określać rozmiaru elementów sterujących, paneli układu, ani nawet Sceny, ponieważ system będzie się zmniejszał, aby dopasować się do twoich treści.

Ale to kłamstwo. Lub co najmniej sprzeczne z naturą projektantów. W ten sposób można bezpośrednio wpływać na wielkość kontrolek i regionów, ustawiając ich właściwości „nadpisania” min, pref i max:

button.setPrefWidth(100);

Domyślnie właściwości te mają wartość wskaźnika USE_COMPUTED_SIZE, co powoduje, że metody minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w), maxWidth(h)/maxHeight(w) zwracają wartości wewnętrzne. Ustawienie ich na dodatnią wartość podwójną zastąpi ich wartości wewnętrzne.

Często chcesz, aby minimalny lub maksymalny rozmiar śledził preferowane, więc minimalne i maksymalne nadpisania mogą być również ustawione na USE_PREF_SIZE, co jest oczywiste:

stackpane.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);

Uważaj na subtelne różnice między metodami Node i właściwościami rozmiaru override:

button.getPrefWidth() returns the size override (might be a magic sentinel value) while button.prefWidth(-1) returns the actual preferred width of the button (returning the override if set).  Layout code should always use Node’s minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w), maxWidth(h)/maxHeight(w) methods for layout computations.

button.getPrefWidth () zwraca nadpisanie rozmiaru (może to być magiczna wartość), natomiast button.prefWidth (-1) zwraca rzeczywistą preferowaną szerokość przycisku (zwracanie przesłonięcia, jeśli jest ustawione). Kod układu powinien zawsze wykorzystywać metody minWidth(h)/minHeight(w), prefWidth(h)/prefHeight(w), maxWidth(h)/maxHeight(w) do obliczeń układu.

AppStore: publikacja aplikacji

Notatka: Ten tutorial jest przekładem oryginalnego materiału napisanego przez Gustavo Ambrozio oraz aktualizowany przez Tony Dahbura.

Artykuł podzielony na dwie części, przedstawia kompletny proces publikowania w sklepie Apple. Rozpoczniemy od założenia konta developerskiego a zakończymy na publikacji aplikacji.

Dowiesz się jak:
– wygenerować niezbędne certyfikaty
– jak skonfigurować aplikację
– wysłać aplikację do zaakceptowania

Tutorial opisuje proces tworzenia nowego konta App Store, nowej aplikacji oraz wysłanie jej do akceptacji, poruszając dokładnie każdy krok.

W tutorialu wyślesz aplikację nazwaną Drop Charge, która pochodzi z 2D iOS & tvOS Games by Tutorials. Aplikacja jest zaakceptowana i może być pobrana z App Store tutaj.

Do przejścia tego tutorialu będziesz potrzebował 99 Amerykańskich dolarów (odpowiedni koszt jest wyliczany na podstawie aktualnego kursu waluty), aktywną kartę kredytową oraz przeglądarkę internetową. Będziesz również potrzebować komputera Mac z zainstalowanym systemem OS X oraz zainstalowaną aplikacją Xcode, pobraną ze sklepu Apple.

Pomocna okażę się także cierpliwość i spokój. Rozpoczynając rejestrację dewelopera przygotuj się na długi proces i czasami niezbędne będzie powtarzanie niektórych kroków.
Pamiętaj: na końcu będziesz w stanie publikować aplikacje do sklepu Apple dla potencjalnej fortuny i chwały!

Startujemy:

Pierwszym krokiem na ścieżce do App Store jest rejestracja dewelopera. Muszę Cię poinformować że bycie developerem jest bezpłatne, ale nie jest to tym samym co publikowanie aplikacji, dlatego musisz ponieść wyżej wymienioną kwotę $99.

Jeśli masz już utworzone konto, to możesz śmiało pomiąć ten krok. W przeciwnym razie przejdź do strony  Apple Developer Site i kliknij w prawmy górnym rogu link Konto/Account.

 

Na kolejnej stronie, możesz wybrać kompletnie nowe konto albo wybrać istniejące. Jeśli chcesz, oraz zaoszczędzisz tym samym czas, możesz wybrać własne Apple ID z którego korzystasz dla iTunes, ale lepszym rozwiązaniem jest posiadać osobne identyfikatory(ID), dla rozdzielenia życia osobistego i zawodowego.

Zatem, wybierz utworzenie nowego konta (Create Apple ID):

 

Uzupełnij formularz o adres email, hasło oraz dane wrażliwe. Wybierz adres email, którego często używasz ponieważ firma Apple cyklicznie aktualizuje program developerski i status aplikacji wysłanych do akceptacji.

 

Przewiń w dół i uzupełnij dane o pytanie beżpieczeństwa i  wypełnij captcha, później wybierz Dalej/Continue.

 

Odbierz wiadomość wysłaną na podany wcześniej adres w procesie rejestracji. Wiadomość powinna wyglądać tak jak poniżej.

 

Następna strona poprosi Cię o kod podany we wiadomości, uzupełnij pola i kliknij weryfikację/Verify.

 

Od teraz masz konto developerskie Apple Id. Dobra robota! Zaloguj się do strony dewelopeskiej używając nowego ID.

 

Na następnej stronie należy potwierdzić zgody. Powinno się uzgodnić regulaminy z prawnikiem, jeśli on się zgodzi, możesz śmiało potwierdzić. Możesz także godzić się na otrzymywanie wiadomości marketingowych, zaznacz checkbox jeśli chcesz. Teraz zatwierdź/wyślij formularz.

 

Teraz jesteś deweloperem Apple’a! Super, ale czy możesz już zacząć rozwijać i publikować aplikacje w App Store? Więc, nie zupełnie… Masz dostęp do bibliotek i narzędzi, ale musisz także dać firmie Apple trochę pieniędzy do wysłania aplikacji do sklepu.

 

Przystąp do programu Deweloperskiego zanim wyślesz aplikację

Będąc zarejestrowanym developerem Apple otrzymujesz dostęp do wielu informacji, i możliwość do publikacji w App Store (oraz dostęp do powiązanych portali), po uiszczeniu opłaty w Apple’s Developer Program. W przeszłości były programy dla iOS, OSX oraz Safari. Teraz jest jeden program a poniesiona opłata pokrywa wszystkie platformy. Ta część będzie kosztowała $99 (dolarów amerykańskich) na rok.

Jeśli postępowałeś zgodnie z poprzedniom sekcjom i kliknąłeś Kontynuuj/Continue, powinieneś byś we właściwym miejscu. Jeśli pominąłeś poprzedni krok, ponieważ już konto developerskie Apple, to możesz zalogować się do Developer Member Center i zsynchronizować.

Po pierwszym zalogowaniu, kliknij link Join the Apple Developer Program, w dolnej części strony.

 

Teraz kliknij Enroll/Płatność:

 

Następna strona opisuje sposób płatności jako Indywidualny albo Firmowym. Dla tego tutorialu, zobaczysz jak opłacić konto indywidualne. Jeśli wybierzesz płatności dla firmy, proces nie będzie taki prosty – będzie zmuszony przygotować dużo więcej informacji i udowodnić swoją rolę w firmie.

Weź głęboki oddech, upewnij się że masz pół godziny czasu, i kliknij rozpoczęcie płatności Start Your Enrolment.

 

Następna strona zapyta Cię czy chcesz płacić indywidualnie, jako firma, czy jako organizacja rządowa.Jeśli wybierzesz płatność  jako firma , przeczytaj wymagania, dla pewności że masz wszystko.

Wybierz Individual / Sole Proprietor / Single Person Business, i kliknij Kontynuuj/Continue:

 

Wpisz swoje dane do weryfikacji swojej osoby. Apple podejmie próbę potwierdzenia tych informacji z twoją kartą kredytową, więc upewnij się że wypełniłeś poprawne dane.

 

Wypełnij pozostałe pola i poniżej zobaczysz kolejne zgody licencyjne. Zatem uzgodnij z prawnikiem  czy wszystko jest dobrze i kliknij Kontynuuj/Countinue.

 

Przejrzyj wyświetlone informacje i gdy jesteś gotów wyślij formularz klikając Kontynuuj/Continue.

 

Będziesz teraz poinformowany o kosztach i podsumowaniu zakupu. Masz możliwość automatycznego odnawiania każdego roku, co zabezpiecza przed zapominaniem o odnowieniu, co z kolei ochroni przed wyłączeniem dostępu, gdy jesteś na wakacjach.

Zaznacz Automatic Renewal, jeśli życzysz sobie tę opcję i kliknij Płacę/Purchase:

 

Musisz się ponownie zalogować. Użyj swojego nowo utworzonego Apple ID.

 

Wypełnij formularz poprawnymi danymi według nw. wzorca.

 

Kolejny raz, zostaniesz poproszony o zatwierdzenie Regulaminów i Warunków. Zadzwoń do swojego adwokata i przegadajcie co trzeba… Zaznacz haczyk i kliknij Kontynuuj/Continue.

 

Confirm your intent to purchase the membership:

 

Po wszystkim dostajesz podziękowanie.

 

Nie ma za co!

Teraz, wpuść mnie!

Po wysłaniu formularza i opłaceniu rejestracji w iOS Developer, będziesz musiał poczekać jakiś czas na realizację zamówienia w Apple.

W przypadku, gdy w kraju nie ma siedziby Apple, dane muszą być wysłane faxem i czas oczekiwania może się wydłużyć.

W każdym razie, powinieneś otrzymać email od Apple taki jak poniżej:

 

W tym samym czasie, powinieneś otrzymać też:

 

W tym momencie powinieneś pobrać Xcode z Apple App Store, wykorzystując ikonę App Store na pasku „dock”. Firma Apple publikuje ostatniom nie-beta wersję w App Store. Wyszukaj Xcode lub kliknij tutaj. Ten tutorial porusza temat Xcode powierzchownie, jest wiele dobrych stron opisujących np. RayWenderlich.com .

Przejdź teraz do Developer Center i się zaloguj:

 

Po uzupełnieniu danych, nareszcie będziesz!

 

Centrum Deweloperów zawiera mnóstwo informacji. Znajdują się poradniki programistyczne, kody do pobrania, dokumentacja, nagrania wideo, oraz bardzo przydatne forum programistów i centrum pomocy.

Poświęć trochę czasu na przegląd z dostępnymi materiałami. Bądź świadomym że niektóre informacje mogą być poufne, specjalnie gdy obejmują wersje beta każdego z SDK albo narzędzi.

W tym tutorialu jest przedstawiony sposób jak wysłać aplikację. Skupimy się na dwóch obszarach, które wykorzystasz przy rozwijaniu aplikacji: Certificates, IDs & Profiles oraz iTunes Connect:

 

Ale pierw, krótkie wprowadzenie do każdej z nich.

Certificates, IDs & Profiles

Jak już możesz wiedzieć, proces urządzeń non-jailbroken iOS jest tylko dostępny do uruchomienia akceptacji aplikacji przez Apple i instalacji w App Store.

Archiwa Apple wymagają aby każda działająca aplikacja była podpisana certyfikatami Apple. Aplikacje zainstalowane z App Store pochodzą w pakiecie z certyfikatami, które system weryfikuje przed tym jak dopuści je do uruchomienia. Jeśli nie ma podpisu lub jest one nieprawidłowy, aplikacja nie będzie uruchomiona.

Jako deweloper, musisz być zdolny do uruchomienia własnej aplikacji na posiadanym urządzeniu, na które będzie przeznaczone. Dlatego musisz utworzyć  i podpisać własnym certyfikatem.

Do tego służy dział Certificates, IDs & Profiles. Ta sekcja dopuszcza Cię do generowania kluczy, które Apple nazywa „profilami”. Profile, czasem są nazywane „identyfikatorami podpisu kodu”, są plikami wygenerowanymi przez Developer Center, które umożliwiają Xcode podpisanie twojej aplikacji w sposób, który dopuści instalację na testowym urządzeniu.

Są dwa typy profili:

  • Profil deweloperski.  Jest on związany ze specyfikacją urządzenia, więc aplikacja może być uruchomiona tylko na nim.
  • Profil dystrybucyjny. Ten z kolei służy do podpisu aplikacji przed wysłaniem jej do Apple do zakceptacji. Nie zawiera informacji o specyfikacji urządzenia, ale nie możesz ich używać  do instalacji aplikacji na każdym urządzeniu samodzielnie, ponieważ Apple wymaga podpisu aplikacji po procesie akceptacji.

Certificates, IDs & Profiles, może także wygenerować certyfikat push certificates,  w przypadku gdy aplikacja będzie wysyłać powiadomienia.

Połączenie iTunes

Połączenie iTunes jest portalem, którego użyjesz do wysłania aplikacji. Jest to miejsce gdzie zarejestrujesz nową aplikację, zatwierdzisz opis i screenshoty, wybierzesz cenę, skonfigurujesz centrum gier, zakupy w aplikacji.

Jet to także portal, w którym podpiszesz zgody na nowe kontrakty, uzupełnisz dane płatności i sprawdzisz sprzedaż.

Dalsza część tutorialu opisuje współpracę z działem Certificates, IDs & Profiles. W części drugiej przyjrzymy się iTunes Connect.

Certificates, IDs and Profiles

W dalszej części tutorialu, skorzystamy z działu Certificates, IDs and Profiles do ustawienia informacji niezbędnych do uruchomienia aplikacji na urządzeniu (a później na App Store).

Wiedz że jest prosta droga tego poprzez Xcode wykorzystując Automatic Device Provisioning, co pokrywa drugą część serii. Ale na razie, przejdziemy ten proces krok po kroku. Zrozumiesz jak działają lepiej w tą drogą, i jest to bardzo pomocna wiedza kiedy będziesz wysyłać aplikację do App Store.

Jeśli jeszcze jesteś na stronie Centrum Deweloperskiego, kliknij link Certificates, IDs & Profiles w lewej części albo kliknij ikonę zębatki po środku strony:

 

Możesz zrobić mnóstwo rzeczy z tego poziomu. Kilka z nich będziesz robić tylko raz, jak generowanie certyfikatów czy rejestrowanie urządzeń. Inne rzeczy będą potarzane dla każdej aplikacji, jak generowanie profili developerskich czy dystrybucyjnych.

Generowanie Certyfikatu

Na początku musisz wygenerować dwa certyfikaty, jeden z profilu deweloperskiego a drugi z profilu dystrybucji. Opisany tekst na stronie wyjaśnia, w jaki sposób wysłać zapytanie o certyfikat poprzez Xcode, lub ręcznie. Ale jest bardzo użytecznym dla ciebie zrozumienie procesu ręcznego, więc będziesz wysyłał Certificate Signing Request (albo CSR) ze swojego Mac’a.

Upewnij się że lista rozwijana w lewej górnej części zawiera iOS, tvOS, watchOS, później kliknij plus (+) po prawej stronie:

 

Na następnej stronie, wybierz iOS App Development jako typ certyfikatu i kliknij Konynuuj/Continue na dole:

 

 

Poniżej wyjaśnienie jak wygenerować CSR wykorzystując Pęk kluczy/Keychain access. Zgodnie z instrukcją, musisz otworzyć Pęk kluczy na Mac’u. Jeśli nie wiesz gdzie go znaleźć, wykorzystaj wyszukiwanie Spotlight(lupa na górnym pasku po prawej stronie):

 

Gdy program się uruchomi, wybierz Keychain Access\Certificate Assistant\Request a Certificate From a Certificate Authority…/ Wniosek o wydanie certyfikatu z urzędu certyfikacji…:

 

W oknie Asystenta Certyfikacji wypełnij swój adres email i personalia, wybierz Zapisz na dysku i kliknij Kontynuuj.

 

Zapisz plik gdziekolwiek na komputerze. To jest twój utworzony CSR, teraz trzeba wygenerować certyfikat.

Przejdź do Developer Centre w przeglądarce – powinieneś teraz kliknąć Kontynuuj:

 

Kliknij  Wybierz Plik…, wybierz swój plik CSR, który właśnie utworzyłeś i kliknij Kontynuuj:

 

Zobaczysz teraz ekran oznajmujący że twój certyfikat jest gotowy. Kliknij Pobierz/Download, zainstaluj go w pęku kluczy poprzez podwójne kliknięcie pobranego pliku:

 

Kliknij Dodaj/Add w okienku Pęku Kluczy aby dokończyć instalację:

 

Teraz masz swój certyfikat do profilu deweloperskiego, teraz należy utworzyć certyfikat do profilu produkcyjnego albo dystrybucji. Kliknij przycisk Dodaj Kolejny/Add Another. Pod produkcjom wybierz guzik App Store and Ad Hoc, i kliknij Kontynuuj/Countinue poniżej:

 

Przejdź ten sam proces jak poprzednio do zatwierdzenia zapytania o podpis jak zrobiłeś to z poprzednim dewelopeskim certyfikatem.

Kiedy jest gotowy, kliknij Pobierz, i zainstaluj dystrybucję certyfikatu poprzez podwójne kliknięcie:

 

Notatka: Certyfikat dystrybucji nazywa się ios_distribution.cer, natomiast certyfikat deweloperski, który pobrałeś wcześniej nazwij ios_developement.cer. 

Notatka: Może zauważyłeś informacje poniżej przycisku „Continue”, mówiącym o Intermediate Certificates. Kiedy uruchomisz Xcode, lub gdy masz go włączony, będzie automatycznie instalował dla ciebie. Kiedy będziesz potrzebował zainstalować w przyszłości z jakichkolwiek powodów, po prostu kliknij plus (+), jako kreator nowego certyfikatu i przewiń w dół do pobrania pliku:

 

Pobierze to plik nazwany AppleWWDRCA.cer. Podwójny klik na tym pliku zainstaluje go. Otworzy się ponownie Pęk kluczy, gdy zamknąłeś.

Spójrz teraz na Pęk kluczy, zobaczysz że masz zainstalowane dwa certyfikaty jak poniżej:

 

Notatka: Jeśli nie widzisz wiadomości Ten certyfikat jest ważny, z zielonym znacznikiem, nie możesz jeszcze uruchamiać Xcode jeszcze, albo musisz zainstalować Certyfikaty Pośrednie, opisane powyżej. Najprostszym krokiem jest uruchomienie Xcode i niech on aktualizuje wszystkie certyfikaty dla ciebie.

Teraz możesz zakończyć Pęk kluczy.

Rejestracja Urządzenia

Następnym krokiem jest rejestracja urządzenia. Z lewego menu menu wybierz Device/All a później po prawo plusik (+):

 

Będziesz potrzebował UDID twojego urządzenia/urządzeń na którym chcesz uruchamiać aplikację do testów. Jest kilka sposobów na uzyskanie informacji o UDID: są darmowe aplikacje, które pobiorą je dla ciebie, albo możesz wykorzystać organizer Xcode. Ale my wykorzystamy iTunes.

Otwórz iTunes and podłącz urządzenie do komputera. Wybierz urządzenie z paska menu w kontrolerze odtwarzaczy. iTunes wyświetli nazwę urządzenia, pojemność, wersje, numer seryjny, numer telefonu. Kliknij numer seryjny i wtedy zmieni się w UDID urządzenia:

 

Teraz po prostu zaznacz wyświetlony numer i skopiuj go.

Wróć do przeglądarki, wpisz nazwę urządzenia (może być dowolna jaką chcesz) i wklej kod UDID w odpowiednie pole. Po wszystkim kliknij Kontynuuj/Continue:

 

Będziesz teraz poproszony o potwierdzenie rejestracji. Kliknij Rejestrację:

 

Twoje urządzenie jest już zarejestrowane i pojawi się na liście dostępnych urządzeń:

 

Możesz za wsze powrócić później do rejestracji urządzeń o dodać kolejne, należące do znajomych i beta-testerów.

Notatka: Apple umożliwia ci rejestrację do 100 urządzeń na rok do konta. Jeśli zarejestrujesz urządzenie a później usuniesz je, to dalej będzie widoczne w liczniku rocznym.

Tworzenie Identyfikatora Aplikacji

Teraz masz zarejestrowane urządzenie i trzeba utworzyć App ID. Każda aplikacja, nad którą pracujesz będzie miała własne App Id. Kliknij Identifiers\App IDs, w lewym menu:

 

Zobaczysz notatkę wyjaśniającą koncepcję korzystania z App ID. W skrócie, App Id jest kombinacją 10 znaków prefix „seed” wygenerowany przez Apple, i suffix utworzony przez ciebie, definicję pakietu identyfikatora do wyszukiwania. Razem tworzą unikalny identyfikator dla twojej aplikacji.

Jest kilka istotnych informacji o App ID:

  • Możesz zdecydować aby każda aplikacja była udostępniana z tym samym prefixem, jeśli. chcesz współdzielić informacje między nimi. Powiedzmy że masz nadrzędną aplikację wykorzystującą logowanie tym samym loginem. Jeśli aplikacje wykorzystują ten sam prefix i jedna aplikacja zapisuje login do kluczy iOS, a inna aplikacja pobiera ten klucz w celu zalogowania.
  • Możesz utworzyć dwa różne typy App ID:  Explicit App ID, lub Wildcard App ID. Explicit App ID musi by wykorzystane kiedy chcesz włączyć serwisy takie jak płatności w aplikacji lub iCloud. Wildcard App Id powinien być używany kiedy chcesz używać tego samego App ID w kilku aplikacjach.
  • Explicit App ID, tekst do wyszukiwania Bundle ID musi być unikalny dla każdej aplikacji. Będzie wykorzystywany dla serwisu wysyłania powiadomień, wewnątrz aplikacji do płatności  i innych serwisów jak magazyn iCloud.
  • Apple rekomenduje używanie stylu nazywania „reverse-demain” dla nazywania paczek. Dla przykładu dla Explicit App ID, sugerowanym formatem jest „com.domainname.appname”, dla Wildcartd App ID, jest to „com.domainname.*”.
  • Pamiętaj, jeśli użyjesz Wildcard App ID, nie będziesz mógł korzystać z niektórych normalnie dostępnych serwisów, takich jak wysyłanie powiadomień czy przeprowadzenie płatności w aplikacji. Pewnie nie myślałeś o używaniu tych serwisów teraz, ale jeśli zmienisz zdanie, nie będziesz mógł tego zrobić bez zmiany App ID generując nową aplikację.

Teraz już wiesz wszystko o App ID, wic jest czas na to żeby utworzyć. Po prawej stronie ekranu kliknik plus(+):

 

Wypełnij opis (zazwyczaj jest to nazwa aplikacji). Ten skrót/ID będzie zazwyczaj/zawsze twoim  ID grupy. Upewnij się że wybrałeś Explicit app ID, i wpisz ID paczki/Bundle ID – pamiętaj o używaniu stylu nazwy odwrotnej-domeny, dodając nazwę na końcu łańcucha nazwy. Kliknij kontynuuj gdy wpisze:

 

Będziesz poproszony o potwierdzenie wpisanych wartości, kliknij Rejestruj na dole. Wtedy zobaczysz informację o zakończeniu rejestracji.

 

Teraz jesteś gotowy do utworzenia profilu udostępniania i dystrybucji.

Profil Udostępniania

Na bocznym menu, kliknij Provisioning Profiles\All:

 

Zobaczysz opis wyjaśniający korzystanie z profilu udostępniania iOS. Profil ten łączy w całość wszystkie kawałki w całość, włączając certyfikaty, identyfikatory urządzeń i identyfikator aplikacji.

Profil udostępniania rozwojowy wykorzystuje się do budowania i instalacji kolejnych wersji twojej aplikacji podczas procesu rozwijania a profil dystrybucji jet używany do wysłania aplikacji do App Store i beta testerów.

Po prawej stronie kliknij plus (+):

 

Wybierz iOS App Development, i kliknij Kontynuuj:

 

Na następnym ekranie zostaniesz zapytany o wybór identyfikatora aplikacji dla tego profilu. Jak na razie wygenerowałeś jeden, więc wybierz go z menu i kliknij Kontynuuj:

 

W dalszej części zostaniesz zapytany o wybranie certyfikatów do profilu. Jeśli masz wiele osób w zespole, to mogą być wybrane właśnie tutaj. Wybierz certyfikaty zaznaczając boxy i kliknij Kontynuuj:

 

Ten ekran zapyta cię o profil urządzenia do walidacji, wybierz swój sprzęt i kliknij Kontynuuj:

 

Teraz wpisz nazwę profilu. Nazwa powinna być charakterystyczna do identyfikacji wśród innych profili, spróbuj opisać to w miarę możliwości. Kliknij Kontynuuj:

 

Ostatnia strona pokazuje wygenerowane profile i posiada przycisk pobierania, który umożliwia pobranie. Zatem dalej i kliknij Pobierz:

 

Od czasu przejścia do tej strony, idź dalej i wygeneruj profil dystrybucyjny. Nie będziesz go właściwie potrzebował do czasu gdy nie zechcesz wysłać aplikacji do zaakceptowania, ale gdy tu już jesteś to warto to zrobić. Kliknij przycisk Add Another na dole strony:

 

Z listy dystrybucji wybierz App Store i kliknij Kontynnuj:

 

Kilka kolejnych kroków  są takie same jak dla profilu deweloperskiego. Podążając  po ekranie, nazwij profil dystrybucyjny w mirę opisowo, unikalnie i pobierz jak zrobiłeś to poprzednio:

Teraz odnajdź pliki, które właśnie pobrałeś na swój komputer, i podwójnym kliknięciem każdy uruchomi Xcode. Zweryfikuj profile otwarte w projekcie albo utwórz nowy dla testów. Kliknij na projekt na panelu po lewej stronie. Wybierz Build Settings, wybierz All, przewiń w dół do Code Signing i kliknij słowo Automatic dalej dla wpisania Provisioning Profile. Twoje profile powinny być wyświetlone: