tools: new installer for Windows

This commit is contained in:
Ivan Grokhotkov 2019-04-29 10:36:03 +08:00
parent 12b6da0388
commit 04d24c750a
21 changed files with 2263 additions and 231 deletions

7
.gitignore vendored
View File

@ -58,13 +58,6 @@ TEST_LOGS
coverage.info coverage.info
coverage_report/ coverage_report/
# Windows tools installer build
tools/windows/tool_setup/.*
tools/windows/tool_setup/input
tools/windows/tool_setup/dl
tools/windows/tool_setup/keys
tools/windows/tool_setup/Output
test_multi_heap_host test_multi_heap_host
# VS Code Settings # VS Code Settings

6
tools/windows/tool_setup/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
Output
cmdlinerunner/build
dist
unzip
keys
idf_versions.txt

View File

@ -0,0 +1,39 @@
# ESP-IDF Tools Installer for Windows
This directory contains source files required to build the tools installer for Windows.
The installer is built using [Inno Setup](http://www.jrsoftware.org/isinfo.php). At the time of writing, the installer can be built with Inno Setup version 6.0.2.
The main source file of the installer is `idf_tools_setup.iss`. PascalScript code is split into multiple `*.iss.inc` files.
Some functionality of the installer depends on additional programs:
* [Inno Download Plugin](https://bitbucket.org/mitrich_k/inno-download-plugin) — used to download additional files during the installation.
* [7-zip](https://www.7-zip.org) — used to extract downloaded IDF archives.
* [cmdlinerunner](cmdlinerunner/cmdlinerunner.c) — a helper DLL used to run external command line programs from the installer, capture live console output, and get the exit code.
## Steps required to build the installer
* Build cmdlinerunner DLL.
- On Linux/Mac, install mingw-w64 toolchain (`i686-w64-mingw32-gcc`). Then build the DLL using CMake:
```
mkdir -p cmdlinerunner/build
cd cmdlinerunner/build
cmake -DCMAKE_TOOLCHAIN_FILE=../toolchain-i686-w64-mingw32.cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
```
This will produce `cmdlinerunner.dll` in the build directory.
- On Windows, it is possible to build using Visual Studio, with CMake support installed. By default, VS produces build artifacts in some hard to find directory. You can adjust this in CmakeSettings.json file generated by VS.
* Download 7zip.exe [("standalone console version")](https://www.7-zip.org/download.html) and put it into `unzip` directory (to get `unzip/7za.exe`).
* Download [idf_versions.txt](https://dl.espressif.com/dl/esp-idf/idf_versions.txt) and place it into the current directory. The installer will use it as a fallback, if it can not download idf_versions.txt at run time.
* Create the `dist` directory and populate it with the tools which should be bundled with the installer. At the moment the easiest way to obtain it is to use `install.sh`/`install.bat` in IDF, and then copy the contents of `$HOME/.espressif/dist` directory. If the directory is empty, the installer should still work, and the tools will be downloaded during the installation.
* Build the installer using Inno Setup Compiler: `ISCC.exe idf_tools_setup.iss`.
* Obtain the signing keys, then sign `Output/esp-idf-tools-setup-unsigned.exe`.

View File

@ -0,0 +1,247 @@
var
ChoicePagePrepare: array of TNotifyEvent;
ChoicePageSelectionChange: array of TNotifyEvent;
ChoicePageValidate: array of TWizardPageButtonEvent;
ChoicePageMaxTag: Integer;
ChoicePages: array of TInputOptionWizardPage;
procedure ChoicePageOnClickCheck(Sender: TObject);
var
ListBox: TNewCheckListBox;
Id: Integer;
begin
ListBox := TNewCheckListBox(Sender);
Id := Integer(ListBox.Tag);
ChoicePageSelectionChange[Id](ChoicePages[Id]);
end;
function ChoicePageGetInput(Page: TInputOptionWizardPage): TNewEdit;
begin
Result := TNewEdit(Page.FindComponent('ChoicePageInput'));
end;
function ChoicePageGetLabel(Page: TInputOptionWizardPage): TNewStaticText;
begin
Result := TNewStaticText(Page.FindComponent('ChoicePageLabel'));
end;
function ChoicePageGetButton(Page: TInputOptionWizardPage): TNewButton;
begin
Result := TNewButton(Page.FindComponent('ChoicePageBrowseButton'));
end;
procedure ChoicePageSetEditLabel(Page: TInputOptionWizardPage; Caption: String);
var
InputLabel: TNewStaticText;
begin
InputLabel := ChoicePageGetLabel(Page);
InputLabel.Caption := Caption;
end;
function ChoicePageGetInputText(Page: TInputOptionWizardPage): String;
begin
Result := ChoicePageGetInput(Page).Text;
end;
procedure ChoicePageSetInputText(Page: TInputOptionWizardPage; Text: String);
begin
ChoicePageGetInput(Page).Text := Text;
end;
procedure ChoicePageSetInputEnabled(Page: TInputOptionWizardPage; Enabled: Boolean);
begin
ChoicePageGetLabel(Page).Enabled := Enabled;
ChoicePageGetInput(Page).Enabled := Enabled;
ChoicePageGetButton(Page).Enabled := Enabled;
end;
procedure ChoicePageOnBrowseButtonClick(Sender: TObject);
var
Button: TNewButton;
Page: TInputOptionWizardPage;
InputLabel: TNewStaticText;
Input: TNewEdit;
Dir: String;
begin
Button := TNewButton(Sender);
Page := TInputOptionWizardPage(Button.Owner);
Input := ChoicePageGetInput(Page);
InputLabel := ChoicePageGetLabel(Page);
Dir := Input.Text;
if BrowseForFolder(InputLabel.Caption, Dir, True) then
begin
Input.Text := Dir;
end;
end;
<event('CurPageChanged')>
procedure ChoicePageOnCurPageChanged(CurPageID: Integer);
var
i: Integer;
begin
for i := 1 to ChoicePageMaxTag do
begin
if ChoicePages[i].ID = CurPageID then
begin
ChoicePagePrepare[i](ChoicePages[i]);
break;
end;
end;
end;
<event('NextButtonClick')>
function ChoicePageOnNextButtonClick(CurPageID: Integer): Boolean;
var
i: Integer;
begin
Result := True;
for i := 1 to ChoicePageMaxTag do
begin
if ChoicePages[i].ID = CurPageID then
begin
Result := ChoicePageValidate[i](ChoicePages[i]);
break;
end;
end;
end;
<event('InitializeWizard')>
procedure InitChoicePages();
begin
ChoicePages := [ ];
ChoicePagePrepare := [ ];
ChoicePageSelectionChange := [ ];
ChoicePageValidate := [ ];
end;
function FindLinkInText(Text: String): String;
var
Tmp: String;
LinkStartPos, LinkEndPos: Integer;
begin
Result := '';
Tmp := Text;
LinkStartPos := Pos('https://', Tmp);
if LinkStartPos = 0 then exit;
Delete(Tmp, 1, LinkStartPos - 1);
{ Try to find the end of the link }
LinkEndPos := 0
if LinkEndPos = 0 then LinkEndPos := Pos(' ', Tmp);
if LinkEndPos = 0 then LinkEndPos := Pos(',', Tmp);
if LinkEndPos = 0 then LinkEndPos := Pos('.', Tmp);
if LinkEndPos = 0 then LinkEndPos := Length(Tmp);
Delete(Text, LinkEndPos, Length(Tmp));
Log('Found link in "' + Text + '": "' + Tmp + '"');
Result := Tmp;
end;
procedure OnStaticTextClick(Sender: TObject);
var
StaticText: TNewStaticText;
Link: String;
Err: Integer;
begin
StaticText := TNewStaticText(Sender);
Link := FindLinkInText(StaticText.Caption);
if Link = '' then
exit;
ShellExec('open', Link, '', '', SW_SHOWNORMAL, ewNoWait, Err);
end;
procedure MakeStaticTextClickable(StaticText: TNewStaticText);
begin
if FindLinkInText(StaticText.Caption) = '' then
exit;
StaticText.OnClick := @OnStaticTextClick;
StaticText.Cursor := crHand;
end;
function ChoicePageCreate(
const AfterID: Integer;
const Caption, Description, SubCaption, EditCaption: String;
HasDirectoryChooser: Boolean;
Prepare: TNotifyEvent;
SelectionChange: TNotifyEvent;
Validate: TWizardPageButtonEvent): TInputOptionWizardPage;
var
VSpace, Y : Integer;
ChoicePage: TInputOptionWizardPage;
InputLabel: TNewStaticText;
Input: TNewEdit;
Button: TNewButton;
begin
ChoicePageMaxTag := ChoicePageMaxTag + 1;
VSpace := ScaleY(8);
ChoicePage := CreateInputOptionPage(AfterID, Caption,
Description, SubCaption, True, True);
MakeStaticTextClickable(ChoicePage.SubCaptionLabel);
ChoicePage.Tag := ChoicePageMaxTag;
ChoicePage.CheckListBox.OnClickCheck := @ChoicePageOnClickCheck;
ChoicePage.CheckListBox.Tag := ChoicePageMaxTag;
if HasDirectoryChooser then
begin
ChoicePage.CheckListBox.Anchors := [ akLeft, akTop, akRight ];
ChoicePage.CheckListBox.Height := ChoicePage.CheckListBox.Height - ScaleY(60);
Y := ChoicePage.CheckListBox.Top + ChoicePage.CheckListBox.Height + VSpace;
InputLabel := TNewStaticText.Create(ChoicePage);
with InputLabel do
begin
Top := Y;
Anchors := [akTop, akLeft, akRight];
Caption := EditCaption;
AutoSize := True;
Parent := ChoicePage.Surface;
Name := 'ChoicePageLabel';
end;
MakeStaticTextClickable(InputLabel);
Y := Y + InputLabel.Height + VSpace;
Input := TNewEdit.Create(ChoicePage);
with Input do
begin
Top := Y;
Anchors := [akTop, akLeft, akRight];
Parent := ChoicePage.Surface;
Name := 'ChoicePageInput';
Text := '';
end;
Button := TNewButton.Create(ChoicePage);
with Button do
begin
Anchors := [akTop, akRight];
Parent := ChoicePage.Surface;
Width := WizardForm.NextButton.Width;
Height := WizardForm.NextButton.Height;
Top := Y - (Height - Input.Height) / 2;
Left := ChoicePage.SurfaceWidth - Button.Width;
Name := 'ChoicePageBrowseButton';
Caption := SetupMessage(msgButtonWizardBrowse);
OnClick := @ChoicePageOnBrowseButtonClick;
end;
Input.Width := Button.Left - ScaleX(8);
end;
SetArrayLength(ChoicePages, ChoicePageMaxTag+1);
SetArrayLength(ChoicePagePrepare, ChoicePageMaxTag+1);
SetArrayLength(ChoicePageSelectionChange, ChoicePageMaxTag+1);
SetArrayLength(ChoicePageValidate, ChoicePageMaxTag+1);
ChoicePages[ChoicePageMaxTag] := ChoicePage;
ChoicePagePrepare[ChoicePageMaxTag] := Prepare;
ChoicePageSelectionChange[ChoicePageMaxTag] := SelectionChange;
ChoicePageValidate[ChoicePageMaxTag] := Validate;
Result := ChoicePage;
end;

View File

@ -0,0 +1,154 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Progress & log page for command line tools ------------------------------ }
var
CmdlineInstallCancel: Boolean;
{ ------------------------------ Splitting strings into lines and adding them to TStrings ------------------------------ }
procedure StringsAddLine(Dest: TStrings; Line: String; var ReplaceLastLine: Boolean);
begin
if ReplaceLastLine then
begin
Dest.Strings[Dest.Count - 1] := Line;
ReplaceLastLine := False;
end else begin
Dest.Add(Line);
end;
end;
procedure StrSplitAppendToList(Text: String; Dest: TStrings; var LastLine: String);
var
pCR, pLF, Len: Integer;
Tmp: String;
ReplaceLastLine: Boolean;
begin
if Length(LastLine) > 0 then
begin
ReplaceLastLine := True;
Text := LastLine + Text;
end;
repeat
Len := Length(Text);
pLF := Pos(#10, Text);
pCR := Pos(#13, Text);
if (pLF > 0) and ((pCR = 0) or (pLF < pCR) or (pLF = pCR + 1)) then
begin
if pLF < pCR then
Tmp := Copy(Text, 1, pLF - 1)
else
Tmp := Copy(Text, 1, pLF - 2);
StringsAddLine(Dest, Tmp, ReplaceLastLine);
Text := Copy(Text, pLF + 1, Len)
end else begin
if (pCR = Len) or (pCR = 0) then
begin
break;
end;
Text := Copy(Text, pCR + 1, Len)
end;
until (pLF = 0) and (pCR = 0);
LastLine := Text;
if pCR = Len then
begin
Text := Copy(Text, 1, pCR - 1);
end;
if Length(LastLine) > 0 then
begin
StringsAddLine(Dest, Text, ReplaceLastLine);
end;
end;
{ ------------------------------ The actual command line install page ------------------------------ }
procedure OnCmdlineInstallCancel(Sender: TObject);
begin
CmdlineInstallCancel := True;
end;
function DoCmdlineInstall(caption, description, command: String): Boolean;
var
CmdlineInstallPage: TOutputProgressWizardPage;
Res: Integer;
Handle: Longword;
ExitCode: Integer;
LogTextAnsi: AnsiString;
LogText, LeftOver: String;
Memo: TNewMemo;
PrevCancelButtonOnClick: TNotifyEvent;
begin
CmdlineInstallPage := CreateOutputProgressPage('', '')
CmdlineInstallPage.Caption := caption;
CmdlineInstallPage.Description := description;
Memo := TNewMemo.Create(CmdlineInstallPage);
Memo.Top := CmdlineInstallPage.ProgressBar.Top + CmdlineInstallPage.ProgressBar.Height + ScaleY(8);
Memo.Width := CmdlineInstallPage.SurfaceWidth;
Memo.Height := ScaleY(120);
Memo.ScrollBars := ssVertical;
Memo.Parent := CmdlineInstallPage.Surface;
Memo.Lines.Clear();
CmdlineInstallPage.Show();
try
WizardForm.CancelButton.Visible := True;
WizardForm.CancelButton.Enabled := True;
PrevCancelButtonOnClick := WizardForm.CancelButton.OnClick;
WizardForm.CancelButton.OnClick := @OnCmdlineInstallCancel;
CmdlineInstallPage.SetProgress(0, 100);
CmdlineInstallPage.ProgressBar.Style := npbstMarquee;
ExitCode := -1;
Memo.Lines.Append('Running command: ' + command);
Handle := ProcStart(command, ExpandConstant('{tmp}'))
if Handle = 0 then
begin
Log('ProcStart failed');
ExitCode := -2;
end;
while (ExitCode = -1) and not CmdlineInstallCancel do
begin
ExitCode := ProcGetExitCode(Handle);
SetLength(LogTextAnsi, 4096);
Res := ProcGetOutput(Handle, LogTextAnsi, 4096)
if Res > 0 then
begin
SetLength(LogTextAnsi, Res);
LogText := LeftOver + String(LogTextAnsi);
StrSplitAppendToList(LogText, Memo.Lines, LeftOver);
end;
CmdlineInstallPage.SetProgress(0, 100);
Sleep(10);
end;
ProcEnd(Handle);
finally
Log('Done, exit code=' + IntToStr(ExitCode));
Log('--------');
Log(Memo.Lines.Text);
Log('--------');
if CmdlineInstallCancel then
begin
MsgBox('Installation has been cancelled.', mbError, MB_OK);
Result := False;
end else if ExitCode <> 0 then
begin
MsgBox('Installation has failed with exit code ' + IntToStr(ExitCode), mbError, MB_OK);
Result := False;
end else begin
Result := True;
end;
CmdlineInstallPage.Hide;
CmdlineInstallPage.Free;
WizardForm.CancelButton.OnClick := PrevCancelButtonOnClick;
end;
if not Result then
RaiseException('Installation has failed at step: ' + caption);
end;

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.5)
project(cmdlinerunner)
set(CMAKE_EXE_LINKER_FLAGS " -static")
add_library(cmdlinerunner SHARED cmdlinerunner.c)
target_compile_definitions(cmdlinerunner PUBLIC UNICODE _UNICODE)
set_target_properties(cmdlinerunner PROPERTIES PREFIX "")
set_target_properties(cmdlinerunner PROPERTIES C_STANDARD 99)
target_link_libraries(cmdlinerunner "-static-libgcc")

View File

@ -0,0 +1,194 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at",
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
#define CMDLINERUNNER_EXPORTS
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "cmdlinerunner.h"
#define LINESIZE 1024
#ifdef WITH_DEBUG
#include <stdio.h>
#define DEBUGV(...) do { fprintf(stderr, __VA_ARG__); } while(0)
#else
#define DEBUGV(...)
#endif
struct proc_instance_s {
PROCESS_INFORMATION child_process;
HANDLE pipe_server_handle;
HANDLE pipe_client_handle;
};
#ifdef WITH_DEBUG
static void print_last_error()
{
DWORD dw;
TCHAR errmsg[LINESIZE];
dw = GetLastError();
FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
errmsg, sizeof(errmsg) - 1, NULL );
DEBUGV("error %d: %s\n", dw, errmsg);
}
#define PRINT_LAST_ERROR() print_last_error()
#else
#define PRINT_LAST_ERROR()
#endif
static proc_instance_t *proc_instance_allocate()
{
return (proc_instance_t*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(proc_instance_t));
}
static void proc_instance_free(proc_instance_t *instance)
{
if (instance->pipe_server_handle) {
CloseHandle(instance->pipe_server_handle);
}
if (instance->pipe_client_handle) {
CloseHandle(instance->pipe_client_handle);
}
if (instance->child_process.hProcess) {
TerminateProcess(instance->child_process.hProcess, 1);
CloseHandle(instance->child_process.hProcess);
CloseHandle(instance->child_process.hThread);
}
HeapFree(GetProcessHeap(), 0, instance);
}
void proc_end(proc_instance_t *inst)
{
if (inst == NULL) {
return;
}
proc_instance_free(inst);
}
CMDLINERUNNER_API proc_instance_t * proc_start(LPCTSTR cmdline, LPCTSTR workdir)
{
proc_instance_t *inst = proc_instance_allocate();
if (inst == NULL) {
return NULL;
}
SECURITY_ATTRIBUTES sec_attr = {
.nLength = sizeof(SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE,
.lpSecurityDescriptor = NULL
};
LPCTSTR pipename = TEXT("\\\\.\\pipe\\cmdlinerunner_pipe");
inst->pipe_server_handle = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_WAIT, 1, 1024 * 16, 1024 * 16,
NMPWAIT_WAIT_FOREVER, &sec_attr);
if (inst->pipe_server_handle == INVALID_HANDLE_VALUE) {
DEBUGV("inst->pipe_server_handle == INVALID_HANDLE_VALUE\n");
goto error;
}
inst->pipe_client_handle = CreateFile(pipename, GENERIC_WRITE | GENERIC_READ,
0, &sec_attr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (inst->pipe_client_handle == INVALID_HANDLE_VALUE) {
DEBUGV("inst->pipe_client_handle == INVALID_HANDLE_VALUE\n");
goto error;
}
DWORD new_mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
if (!SetNamedPipeHandleState(inst->pipe_server_handle, &new_mode, NULL,
NULL)) {
DEBUGV("SetNamedPipeHandleState failed\n");
goto error;
}
if (!SetHandleInformation(inst->pipe_server_handle, HANDLE_FLAG_INHERIT, 0)) {
DEBUGV("SetHandleInformation failed\n");
goto error;
}
if (!SetHandleInformation(inst->pipe_client_handle, HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT)) {
DEBUGV("SetHandleInformation failed\n");
goto error;
}
STARTUPINFO siStartInfo = {
.cb = sizeof(STARTUPINFO),
.hStdError = inst->pipe_client_handle,
.hStdOutput = inst->pipe_client_handle,
.hStdInput = inst->pipe_client_handle,
.dwFlags = STARTF_USESTDHANDLES
};
size_t workdir_len = 0;
StringCbLength(workdir, STRSAFE_MAX_CCH * sizeof(TCHAR), &workdir_len);
if (workdir_len == 0) {
workdir = NULL;
}
TCHAR cmdline_tmp[LINESIZE];
StringCbCopy(cmdline_tmp, sizeof(cmdline_tmp), cmdline);
if (!CreateProcess(NULL, cmdline_tmp,
NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, workdir, &siStartInfo,
&inst->child_process)) {
DEBUGV("CreateProcess failed\n");
goto error;
}
return inst;
error:
PRINT_LAST_ERROR();
proc_instance_free(inst);
return NULL;
}
int proc_get_exit_code(proc_instance_t *inst)
{
DWORD result;
if (!GetExitCodeProcess(inst->child_process.hProcess, &result)) {
return -2;
}
if (result == STILL_ACTIVE) {
return -1;
}
return (int) result;
}
DWORD proc_get_output(proc_instance_t *inst, LPSTR dest, DWORD sz)
{
DWORD read_bytes;
BOOL res = ReadFile(inst->pipe_server_handle, dest,
sz - 1, &read_bytes, NULL);
if (!res) {
if (GetLastError() == ERROR_NO_DATA) {
return 0;
} else {
PRINT_LAST_ERROR();
return 0;
}
}
dest[read_bytes] = 0;
return read_bytes;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved )
{
return TRUE;
}

View File

@ -0,0 +1,32 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at",
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License
#pragma once
#include <windows.h>
struct proc_instance_s;
typedef struct proc_instance_s proc_instance_t;
#ifdef CMDLINERUNNER_EXPORTS
#define CMDLINERUNNER_API __declspec(dllexport)
#else
#define CMDLINERUNNER_API __declspec(dllimport)
#endif
CMDLINERUNNER_API proc_instance_t * proc_start(LPCTSTR cmdline, LPCTSTR workdir);
CMDLINERUNNER_API int proc_get_exit_code(proc_instance_t *inst);
CMDLINERUNNER_API DWORD proc_get_output(proc_instance_t *inst, LPSTR dest, DWORD sz);
CMDLINERUNNER_API void proc_end(proc_instance_t *inst);
CMDLINERUNNER_API BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved );

View File

@ -0,0 +1,7 @@
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86)
set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@ -0,0 +1,98 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Find installed copies of Git ------------------------------ }
var
InstalledGitVersions: TStringList;
InstalledGitDisplayNames: TStringList;
InstalledGitExecutables: TStringList;
procedure GitVersionAdd(Version, DisplayName, Executable: String);
begin
Log('Adding Git version=' + Version + ' name='+DisplayName+' executable='+Executable);
InstalledGitVersions.Append(Version);
InstalledGitDisplayNames.Append(DisplayName);
InstalledGitExecutables.Append(Executable);
end;
function GetVersionOfGitExe(Path: String; var Version: String; var ErrStr: String): Boolean;
var
VersionOutputFile: String;
Args: String;
GitVersionAnsi: AnsiString;
GitVersion: String;
GitVersionPrefix: String;
Err: Integer;
begin
VersionOutputFile := ExpandConstant('{tmp}\gitver.txt');
DeleteFile(VersionOutputFile);
Args := '/C "' + Path + '" --version >gitver.txt';
Log('Running ' + Args);
if not ShellExec('', 'cmd.exe', Args,
ExpandConstant('{tmp}'), SW_HIDE, ewWaitUntilTerminated, Err) then
begin
ErrStr := 'Failed to get git version, error=' + IntToStr(err);
Log(ErrStr);
Result := False;
exit;
end;
LoadStringFromFile(VersionOutputFile, GitVersionAnsi);
GitVersion := Trim(String(GitVersionAnsi));
GitVersionPrefix := 'git version ';
if Pos(GitVersionPrefix, GitVersion) <> 1 then
begin
ErrStr := 'Unexpected git version format: ' + GitVersion;
Log(ErrStr);
Result := False;
exit;
end;
Delete(GitVersion, 1, Length(GitVersionPrefix));
Version := GitVersion;
Result := True;
end;
procedure FindGitInPath();
var
Args: String;
GitListFile: String;
GitPaths: TArrayOfString;
GitVersion: String;
ErrStr: String;
Err: Integer;
i: Integer;
begin
GitListFile := ExpandConstant('{tmp}\gitlist.txt');
Args := '/C where git.exe >"' + GitListFile + '"';
if not ShellExec('', 'cmd.exe', Args,
'', SW_HIDE, ewWaitUntilTerminated, Err) then
begin
Log('Failed to find git using "where", error='+IntToStr(Err));
exit;
end;
LoadStringsFromFile(GitListFile, GitPaths);
for i:= 0 to GetArrayLength(GitPaths) - 1 do
begin
Log('Git path: ' + GitPaths[i]);
if not GetVersionOfGitExe(GitPaths[i], GitVersion, ErrStr) then
continue;
Log('Git version: ' + GitVersion);
GitVersionAdd(GitVersion, GitVersion, GitPaths[i]);
end;
end;
procedure FindInstalledGitVersions();
begin
InstalledGitVersions := TStringList.Create();
InstalledGitDisplayNames := TStringList.Create();
InstalledGitExecutables := TStringList.Create();
FindGitInPath();
end;

View File

@ -0,0 +1,194 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select Git ------------------------------ }
#include "git_find_installed.iss.inc"
var
GitPage: TInputOptionWizardPage;
GitPath, GitExecutablePath, GitVersion: String;
GitUseExisting: Boolean;
GitSelectionInstallIndex: Integer;
GitSelectionCustomPathIndex: Integer;
function GetGitPath(Unused: String): String;
begin
Result := GitPath;
end;
function GitInstallRequired(): Boolean;
begin
Result := not GitUseExisting;
end;
function GitVersionSupported(Version: String): Boolean;
var
Major, Minor: Integer;
begin
Result := False;
if not VersionExtractMajorMinor(Version, Major, Minor) then
begin
Log('GitVersionSupported: Could not parse version=' + Version);
exit;
end;
{ Need at least git 2.12 for 'git clone --reference' to work with submodules }
if (Major = 2) and (Minor >= 12) then Result := True;
if (Major > 2) then Result := True;
end;
procedure GitCustomPathUpdateEnabled();
var
Enable: Boolean;
begin
if GitPage.SelectedValueIndex = GitSelectionCustomPathIndex then
Enable := True;
ChoicePageSetInputEnabled(GitPage, Enable);
end;
procedure OnGitPagePrepare(Sender: TObject);
var
Page: TInputOptionWizardPage;
FullName: String;
i, Index, FirstEnabledIndex: Integer;
OfferToInstall: Boolean;
VersionToInstall: String;
VersionSupported: Boolean;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnGitPagePrepare');
if Page.CheckListBox.Items.Count > 0 then
exit;
FindInstalledGitVersions();
VersionToInstall := '{#GitVersion}';
OfferToInstall := True;
FirstEnabledIndex := -1;
for i := 0 to InstalledGitVersions.Count - 1 do
begin
VersionSupported := GitVersionSupported(InstalledGitVersions[i]);
FullName := InstalledGitDisplayNames.Strings[i];
if not VersionSupported then
begin
FullName := FullName + ' (unsupported)';
end;
FullName := FullName + #13#10 + InstalledGitExecutables.Strings[i];
Index := Page.Add(FullName);
if not VersionSupported then
begin
Page.CheckListBox.ItemEnabled[Index] := False;
end else begin
if FirstEnabledIndex < 0 then FirstEnabledIndex := Index;
end;
if InstalledGitVersions[i] = VersionToInstall then
begin
OfferToInstall := False;
end;
end;
if OfferToInstall then
begin
Index := Page.Add('Install Git ' + VersionToInstall);
if FirstEnabledIndex < 0 then FirstEnabledIndex := Index;
GitSelectionInstallIndex := Index;
end;
Index := Page.Add('Custom git.exe location');
if FirstEnabledIndex < 0 then FirstEnabledIndex := Index;
GitSelectionCustomPathIndex := Index;
Page.SelectedValueIndex := FirstEnabledIndex;
GitCustomPathUpdateEnabled();
end;
procedure OnGitSelectionChange(Sender: TObject);
var
Page: TInputOptionWizardPage;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnGitSelectionChange index=' + IntToStr(Page.SelectedValueIndex));
GitCustomPathUpdateEnabled();
end;
function OnGitPageValidate(Sender: TWizardPage): Boolean;
var
Page: TInputOptionWizardPage;
Version, ErrStr: String;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnGitPageValidate index=' + IntToStr(Page.SelectedValueIndex));
if Page.SelectedValueIndex = GitSelectionInstallIndex then
begin
GitUseExisting := False;
GitExecutablePath := '';
GitPath := '';
GitVersion := '{#GitVersion}';
Result := True;
end else if Page.SelectedValueIndex = GitSelectionCustomPathIndex then
begin
GitPath := ChoicePageGetInputText(Page);
GitExecutablePath := GitPath + '\git.exe';
if not FileExists(GitExecutablePath) then
begin
MsgBox('Can not find git.exe in ' + GitPath, mbError, MB_OK);
Result := False;
exit;
end;
if not GetVersionOfGitExe(GitExecutablePath, Version, ErrStr) then
begin
MsgBox('Can not determine version of git.exe.' + #13#10
+ 'Please check that this copy of git works from cmd.exe.', mbError, MB_OK);
Result := False;
exit;
end;
Log('Version of ' + GitExecutablePath + ' is ' + Version);
if not GitVersionSupported(Version) then
begin
MsgBox('Selected git version (' + Version + ') is not supported.', mbError, MB_OK);
Result := False;
exit;
end;
Log('Version of git is supported');
GitUseExisting := True;
GitVersion := Version;
end else begin
GitUseExisting := True;
GitExecutablePath := InstalledGitExecutables[Page.SelectedValueIndex];
GitPath := ExtractFilePath(GitExecutablePath);
GitVersion := InstalledGitVersions[Page.SelectedValueIndex];
Result := True;
end;
end;
procedure GitExecutablePathUpdateAfterInstall();
var
GitInstallPath: String;
begin
GitInstallPath := GetInstallPath('SOFTWARE\GitForWindows', 'InstallPath');
if GitInstallPath = '' then
begin
Log('Failed to find Git install path');
exit;
end;
GitPath := GitInstallPath + '\cmd';
GitExecutablePath := GitPath + '\git.exe';
end;
<event('InitializeWizard')>
procedure CreateGitPage();
begin
GitPage := ChoicePageCreate(
wpLicense,
'Git choice', 'Please choose Git version',
'Available Git versions',
'Enter custom location of git.exe',
True,
@OnGitPagePrepare,
@OnGitSelectionChange,
@OnGitPageValidate);
end;

View File

@ -0,0 +1,117 @@
@echo off
:: This script is called from a shortcut (cmd.exe /k export_fallback.bat), with
:: the working directory set to an ESP-IDF directory.
:: Its purpose is to support using the "IDF Tools Directory" method of
:: installation for ESP-IDF versions older than IDF v4.0.
:: It does the same thing as "export.bat" in IDF v4.0.
set IDF_PATH=%CD%
if not exist "%IDF_PATH%\tools\idf.py" (
echo This script must be invoked from ESP-IDF directory.
goto :end
)
if "%~2"=="" (
echo Usage: idf_cmd_init.bat ^<Python directory^> ^<Git directory^>
echo This script must be invoked from ESP-IDF directory.
goto :end
)
set IDF_PYTHON_DIR=%1
set IDF_GIT_DIR=%2
:: Strip quoutes
set "IDF_PYTHON_DIR=%IDF_PYTHON_DIR:"=%"
set "IDF_GIT_DIR=%IDF_GIT_DIR:"=%"
:: Clear PYTHONPATH as it may contain libraries of other Python versions
if not "%PYTHONPATH%"=="" (
echo Clearing PYTHONPATH, was set to %PYTHONPATH%
set PYTHONPATH=
)
:: Add Python and Git paths to PATH
set "PATH=%IDF_PYTHON_DIR%;%IDF_GIT_DIR%;%PATH%"
echo Using Python in %IDF_PYTHON_DIR%
python.exe --version
echo Using Git in %IDF_GIT_DIR%
git.exe --version
:: Check if this is a recent enough copy of ESP-IDF.
:: If so, use export.bat provided there.
:: Note: no "call", will not return into this batch file.
if exist "%IDF_PATH%\export.bat" %IDF_PATH%\export.bat
echo IDF version does not include export.bat. Using the fallback version.
if exist "%IDF_PATH%\tools\tools.json" (
set "IDF_TOOLS_JSON_PATH=%IDF_PATH%\tools\tools.json"
) else (
echo IDF version does not include tools\tools.json. Using the fallback version.
set "IDF_TOOLS_JSON_PATH=%~dp0%tools_fallback.json"
)
if exist "%IDF_PATH%\tools\idf_tools.py" (
set "IDF_TOOLS_PY_PATH=%IDF_PATH%\tools\idf_tools.py"
) else (
echo IDF version does not include tools\idf_tools.py. Using the fallback version.
set "IDF_TOOLS_PY_PATH=%~dp0%idf_tools_fallback.py"
)
echo.
echo Setting IDF_PATH: %IDF_PATH%
echo.
set "OLD_PATH=%PATH%"
echo Adding ESP-IDF tools to PATH...
:: Export tool paths and environment variables.
:: It is possible to do this without a temporary file (running idf_tools.py from for /r command),
:: but that way it is impossible to get the exit code of idf_tools.py.
set "IDF_TOOLS_EXPORTS_FILE=%TEMP%\idf_export_vars.tmp"
python.exe %IDF_TOOLS_PY_PATH% --tools-json %IDF_TOOLS_JSON_PATH% export --format key-value >"%IDF_TOOLS_EXPORTS_FILE%"
if %errorlevel% neq 0 goto :end
for /f "usebackq tokens=1,2 eol=# delims==" %%a in ("%IDF_TOOLS_EXPORTS_FILE%") do (
call set "%%a=%%b"
)
:: This removes OLD_PATH substring from PATH, leaving only the paths which have been added,
:: and prints semicolon-delimited components of the path on separate lines
call set PATH_ADDITIONS=%%PATH:%OLD_PATH%=%%
if "%PATH_ADDITIONS%"=="" call :print_nothing_added
if not "%PATH_ADDITIONS%"=="" echo %PATH_ADDITIONS:;=&echo. %
echo Checking if Python packages are up to date...
python.exe %IDF_PATH%\tools\check_python_dependencies.py
if %errorlevel% neq 0 goto :end
echo.
echo Done! You can now compile ESP-IDF projects.
echo Go to the project directory and run:
echo.
echo idf.py build
echo.
goto :end
:print_nothing_added
echo No directories added to PATH:
echo.
echo %PATH%
echo.
goto :eof
:end
:: Clean up
if not "%IDF_TOOLS_EXPORTS_FILE%"=="" (
del "%IDF_TOOLS_EXPORTS_FILE%" 1>nul 2>nul
)
set IDF_TOOLS_EXPORTS_FILE=
set IDF_PYTHON_DIR=
set IDF_GIT_DIR=
set IDF_TOOLS_PY_PATH=
set IDF_TOOLS_JSON_PATH=
set OLD_PATH=
set PATH_ADDITIONS=

View File

@ -0,0 +1,142 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select the version of ESP-IDF to download ------------------------------ }
var
IDFDownloadPage: TInputOptionWizardPage;
IDFDownloadAvailableVersions: TArrayOfString;
IDFDownloadPath, IDFDownloadVersion: String;
function GetSuggestedIDFDirectory(): String;
var
BaseName: String;
RepeatIndex: Integer;
begin
{ Start with Desktop\esp-idf name and if it already exists,
keep trying with Desktop\esp-idf-N for N=2 and above. }
BaseName := ExpandConstant('{userdesktop}\esp-idf');
Result := BaseName;
RepeatIndex := 1;
while DirExists(Result) do
begin
RepeatIndex := RepeatIndex + 1;
Result := BaseName + '-' + IntToStr(RepeatIndex);
end;
end;
function GetIDFVersionDescription(Version: String): String;
begin
if WildCardMatch(Version, 'v*-beta*') then
Result := 'beta version'
else if WildCardMatch(Version, 'v*-rc*') then
Result := 'pre-release version'
else if WildCardMatch(Version, 'v*') then
Result := 'release version'
else if WildCardMatch(Version, 'release/v*') then
Result := 'release branch'
else if WildCardMatch(Version, 'master') then
Result := 'development branch'
else
Result := '';
end;
procedure DownloadIDFVersionsList();
var
Url: String;
VersionFile: String;
begin
Url := '{#IDFVersionsURL}';
VersionFile := ExpandConstant('{tmp}\idf_versions.txt');
if idpDownloadFile(Url, VersionFile) then
begin
Log('Downloaded ' + Url + ' to ' + VersionFile);
end else begin
Log('Download of ' + Url + ' failed, using a fallback versions list');
ExtractTemporaryFile('idf_versions.txt');
end;
end;
procedure OnIDFDownloadPagePrepare(Sender: TObject);
var
Page: TInputOptionWizardPage;
VersionFile: String;
i: Integer;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnIDFDownloadPagePrepare');
if Page.CheckListBox.Items.Count > 0 then
exit;
DownloadIDFVersionsList();
VersionFile := ExpandConstant('{tmp}\idf_versions.txt');
if not LoadStringsFromFile(VersionFile, IDFDownloadAvailableVersions) then
begin
Log('Failed to load versions from ' + VersionFile);
exit;
end;
Log('Versions count: ' + IntToStr(GetArrayLength(IDFDownloadAvailableVersions)))
for i := 0 to GetArrayLength(IDFDownloadAvailableVersions) - 1 do
begin
Log('Version ' + IntToStr(i) + ': ' + IDFDownloadAvailableVersions[i]);
Page.Add(IDFDownloadAvailableVersions[i] + ' ('
+ GetIDFVersionDescription(IDFDownloadAvailableVersions[i]) + ')');
end;
Page.SelectedValueIndex := 0;
ChoicePageSetInputText(Page, GetSuggestedIDFDirectory());
end;
procedure OnIDFDownloadSelectionChange(Sender: TObject);
var
Page: TInputOptionWizardPage;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnIDFDownloadSelectionChange index=' + IntToStr(Page.SelectedValueIndex));
end;
function OnIDFDownloadPageValidate(Sender: TWizardPage): Boolean;
var
Page: TInputOptionWizardPage;
IDFPath: String;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnIDFDownloadPageValidate index=' + IntToStr(Page.SelectedValueIndex));
IDFPath := ChoicePageGetInputText(Page);
if DirExists(IDFPath) and not DirIsEmpty(IDFPath) then
begin
MsgBox('Directory already exists and is not empty:' + #13#10 +
IDFPath + #13#10 + 'Please choose a different directory.', mbError, MB_OK);
Result := False;
exit;
end;
IDFDownloadPath := IDFPath;
IDFDownloadVersion := IDFDownloadAvailableVersions[Page.SelectedValueIndex];
Result := True;
end;
<event('ShouldSkipPage')>
function ShouldSkipIDFDownloadPage(PageID: Integer): Boolean;
begin
if (PageID = IDFDownloadPage.ID) and not IDFDownloadRequired() then
Result := True;
end;
<event('InitializeWizard')>
procedure CreateIDFDownloadPage();
begin
IDFDownloadPage := ChoicePageCreate(
IDFPage.ID,
'Download ESP-IDF', 'Please choose ESP-IDF version to download',
'For more information about ESP-IDF versions, see' + #13#10 +
'https://docs.espressif.com/projects/esp-idf/en/latest/versions.html',
'Choose a directory to download ESP-IDF to',
True,
@OnIDFDownloadPagePrepare,
@OnIDFDownloadSelectionChange,
@OnIDFDownloadPageValidate);
end;

View File

@ -0,0 +1,111 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select whether to download ESP-IDF, or use an existing copy ------------------------------ }
var
IDFPage: TInputOptionWizardPage;
IDFSelectionDownloadIndex: Integer;
IDFSelectionCustomPathIndex: Integer;
IDFUseExisting: Boolean;
IDFExistingPath: String;
function IDFDownloadRequired(): Boolean;
begin
Result := not IDFUseExisting;
end;
procedure IDFPageUpdateInput();
var
Enable: Boolean;
begin
if IDFPage.SelectedValueIndex = IDFSelectionCustomPathIndex then
Enable := True;
ChoicePageSetInputEnabled(IDFPage, Enable);
end;
procedure OnIDFPagePrepare(Sender: TObject);
var
Page: TInputOptionWizardPage;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnIDFPagePrepare');
if Page.CheckListBox.Items.Count > 0 then
exit;
IDFSelectionDownloadIndex := Page.Add('Download ESP-IDF')
IDFSelectionCustomPathIndex := Page.Add('Use an existing ESP-IDF directory');
Page.SelectedValueIndex := 0;
IDFPageUpdateInput();
end;
procedure OnIDFSelectionChange(Sender: TObject);
var
Page: TInputOptionWizardPage;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnIDFSelectionChange index=' + IntToStr(Page.SelectedValueIndex));
IDFPageUpdateInput();
end;
function OnIDFPageValidate(Sender: TWizardPage): Boolean;
var
Page: TInputOptionWizardPage;
NotSupportedMsg, IDFPath, IDFPyPath, RequirementsPath: String;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnIDFPageValidate index=' + IntToStr(Page.SelectedValueIndex));
if Page.SelectedValueIndex = IDFSelectionDownloadIndex then
begin
IDFUseExisting := False;
Result := True;
end else begin
IDFUseExisting := True;
Result := False;
NotSupportedMsg := 'The selected version of ESP-IDF is not supported:' + #13#10;
IDFPath := ChoicePageGetInputText(Page);
if not DirExists(IDFPath) then
begin
MsgBox('Directory doesn''t exist: ' + IDFPath + #13#10 +
'Please choose an existing ESP-IDF directory', mbError, MB_OK);
exit;
end;
IDFPyPath := IDFPath + '\tools\idf.py';
if not FileExists(IDFPyPath) then
begin
MsgBox(NotSupportedMsg +
'Can not find idf.py in ' + IDFPath + '\tools', mbError, MB_OK);
exit;
end;
RequirementsPath := IDFPath + '\requirements.txt';
if not FileExists(RequirementsPath) then
begin
MsgBox(NotSupportedMsg +
'Can not find requirements.txt in ' + IDFPath, mbError, MB_OK);
exit;
end;
IDFExistingPath := IDFPath;
Result := True;
end;
end;
<event('InitializeWizard')>
procedure CreateIDFPage();
begin
IDFPage := ChoicePageCreate(
wpLicense,
'Download or use ESP-IDF', 'Please choose ESP-IDF version to download, or use an existing ESP-IDF copy',
'Available ESP-IDF versions',
'Choose existing ESP-IDF directory',
True,
@OnIDFPagePrepare,
@OnIDFSelectionChange,
@OnIDFPageValidate);
end;

View File

@ -0,0 +1,255 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Downloading ESP-IDF ------------------------------ }
var
IDFZIPFileVersion, IDFZIPFileName: String;
function GetIDFPath(Unused: String): String;
begin
if IDFUseExisting then
Result := IDFExistingPath
else
Result := IDFDownloadPath;
end;
function GetIDFZIPFileVersion(Version: String): String;
var
ReleaseVerPart: String;
i: Integer;
Found: Boolean;
begin
if WildCardMatch(Version, 'v*') or WildCardMatch(Version, 'v*-rc*') then
Result := Version
else if Version = 'master' then
Result := ''
else if WildCardMatch(Version, 'release/v*') then
begin
ReleaseVerPart := Version;
Log('ReleaseVerPart=' + ReleaseVerPart)
Delete(ReleaseVerPart, 1, Length('release/'));
Log('ReleaseVerPart=' + ReleaseVerPart)
Found := False;
for i := 0 to GetArrayLength(IDFDownloadAvailableVersions) - 1 do
begin
if Pos(ReleaseVerPart, IDFDownloadAvailableVersions[i]) = 1 then
begin
Result := IDFDownloadAvailableVersions[i];
Found := True;
break;
end;
end;
if not Found then
Result := '';
end;
Log('GetIDFZIPFileVersion(' + Version + ')=' + Result);
end;
procedure IDFAddDownload();
var
Url, MirrorUrl: String;
begin
IDFZIPFileVersion := GetIDFZIPFileVersion(IDFDownloadVersion);
if IDFZIPFileVersion <> '' then
begin
Url := 'https://github.com/espressif/esp-idf/releases/download/' + IDFZIPFileVersion + '/esp-idf-' + IDFZIPFileVersion + '.zip';
MirrorUrl := 'https://dl.espressif.com/dl/esp-idf/releases/esp-idf-' + IDFZIPFileVersion + '.zip';
IDFZIPFileName := ExpandConstant('{app}\releases\esp-idf-' + IDFZIPFileVersion + '.zip')
if not FileExists(IDFZIPFileName) then
begin
ForceDirectories(ExpandConstant('{app}\releases'))
Log('Adding download: ' + Url + ', mirror: ' + MirrorUrl + ', destination: ' + IDFZIPFileName);
idpAddFile(Url, IDFZIPFileName);
idpAddMirror(Url, MirrorUrl);
end else begin
Log(IDFZIPFileName + ' already exists')
end;
end;
end;
procedure RemoveAlternatesFile(Path: String);
begin
Log('Removing ' + Path);
DeleteFile(Path);
end;
{
Replacement of the '--dissociate' flag of 'git clone', to support older versions of Git.
'--reference' is supported for submodules since git 2.12, but '--dissociate' only from 2.18.
}
procedure GitRepoDissociate(Path: String);
var
CmdLine: String;
begin
CmdLine := GitExecutablePath + ' -C ' + Path + ' repack -d -a'
DoCmdlineInstall('Finishing ESP-IDF installation', 'Re-packing the repository', CmdLine);
CmdLine := GitExecutablePath + ' -C ' + Path + ' submodule foreach git repack -d -a'
DoCmdlineInstall('Finishing ESP-IDF installation', 'Re-packing the submodules', CmdLine);
FindFileRecusive(Path + '\.git', 'alternates', @RemoveAlternatesFile);
end;
{ Run git reset --hard in the repo and in the submodules, to fix the newlines. }
procedure GitRepoFixNewlines(Path: String);
var
CmdLine: String;
begin
CmdLine := GitExecutablePath + ' -C ' + Path + ' reset --hard';
Log('Resetting the repository: ' + CmdLine);
DoCmdlineInstall('Finishing ESP-IDF installation', 'Updating newlines', CmdLine);
Log('Resetting the submodules: ' + CmdLine);
CmdLine := GitExecutablePath + ' -C ' + Path + ' submodule foreach git reset --hard';
DoCmdlineInstall('Finishing ESP-IDF installation', 'Updating newlines in submodules', CmdLine);
end;
{
There are 3 possible ways how an ESP-IDF copy can be obtained:
- Download the .zip archive with submodules included, extract to destination directory,
then do 'git reset --hard' and 'git submodule foreach git reset --hard' to correct for
possibly different newlines. This is done for release versions.
- Do a git clone of the Github repository into the destination directory.
This is done for the master branch.
- Download the .zip archive of a "close enough" release version, extract into a temporary
directory. Then do a git clone of the Github repository, using the temporary directory
as a '--reference'. This is done for other versions (such as release branches).
}
procedure IDFDownload();
var
CmdLine: String;
IDFTempPath: String;
IDFPath: String;
NeedToClone: Boolean;
Res: Boolean;
begin
IDFPath := IDFDownloadPath;
{ If there is a release archive to download, IDFZIPFileName and IDFZIPFileVersion will be set.
See GetIDFZIPFileVersion function.
}
if IDFZIPFileName <> '' then
begin
if IDFZIPFileVersion <> IDFDownloadVersion then
begin
{ The version of .zip file downloaded is not the same as the version the user has requested.
Will use 'git clone --reference' to obtain the correct version, using the contents
of the .zip file as reference.
}
NeedToClone := True;
end;
ExtractTemporaryFile('7za.exe')
CmdLine := ExpandConstant('{tmp}\7za.exe x -o' + ExpandConstant('{tmp}') + ' -r -aoa ' + IDFZIPFileName);
IDFTempPath := ExpandConstant('{tmp}\esp-idf-') + IDFZIPFileVersion;
Log('Extracting ESP-IDF reference repository: ' + CmdLine);
Log('Reference repository path: ' + IDFTempPath);
DoCmdlineInstall('Extracting ESP-IDF', 'Setting up reference repository', CmdLine);
end else begin
{ IDFZIPFileName is not set, meaning that we will rely on 'git clone'. }
NeedToClone := True;
Log('Not .zip release archive. Will do full clone.');
end;
if NeedToClone then
begin
CmdLine := GitExecutablePath + ' clone --recursive --progress -b ' + IDFDownloadVersion;
if IDFTempPath <> '' then
CmdLine := CmdLine + ' --reference ' + IDFTempPath;
CmdLine := CmdLine + ' https://github.com/espressif/esp-idf.git ' + IDFPath;
Log('Cloning IDF: ' + CmdLine);
DoCmdlineInstall('Downloading ESP-IDF', 'Using git to clone ESP-IDF repository', CmdLine);
if IDFTempPath <> '' then
GitRepoDissociate(IDFPath);
end else begin
Log('Moving ' + IDFTempPath + ' to ' + IDFPath);
if DirExists(IDFPath) then
begin
if not DirIsEmpty(IDFPath) then
begin
MsgBox('Destination directory exists and is not empty: ' + IDFPath, mbError, MB_OK);
RaiseException('Failed to copy ESP-IDF')
end;
Res := RemoveDir(IDFPath);
if not Res then
begin
MsgBox('Failed to remove destination directory: ' + IDFPath, mbError, MB_OK);
RaiseException('Failed to copy ESP-IDF')
end;
end;
Res := RenameFile(IDFTempPath, IDFPath);
if not Res then
begin
MsgBox('Failed to copy ESP-IDF to the destination directory: ' + IDFPath, mbError, MB_OK);
RaiseException('Failed to copy ESP-IDF');
end;
GitRepoFixNewlines(IDFPath);
end;
end;
{ ------------------------------ IDF Tools setup, Python environment setup ------------------------------ }
procedure IDFToolsSetup();
var
CmdLine: String;
IDFPath: String;
IDFToolsPyPath: String;
IDFToolsPyCmd: String;
begin
IDFPath := GetIDFPath('');
IDFToolsPyPath := IDFPath + '\tools\idf_tools.py';
if FileExists(IDFToolsPyPath) then
begin
Log('idf_tools.py exists in IDF directory');
IDFToolsPyCmd := PythonExecutablePath + ' ' + IDFToolsPyPath;
end else begin
Log('idf_tools.py does not exist in IDF directory, using a fallback version');
IDFToolsPyCmd := ExpandConstant(PythonExecutablePath
+ ' {app}\idf_tools_fallback.py'
+ ' --idf-path ' + IDFPath
+ ' --tools {app}\tools_fallback.json');
end;
Log('idf_tools.py command: ' + IDFToolsPyCmd);
CmdLine := IDFToolsPyCmd + ' install';
Log('Installing tools:' + CmdLine);
DoCmdlineInstall('Installing ESP-IDF tools', '', CmdLine);
CmdLine := IDFToolsPyCmd + ' install-python-env';
Log('Installing Python environment:' + CmdLine);
DoCmdlineInstall('Installing Python environment', '', CmdLine);
end;
{ ------------------------------ Start menu shortcut ------------------------------ }
procedure CreateIDFCommandPromptShortcut();
var
Destination: String;
Description: String;
Command: String;
begin
ForceDirectories(ExpandConstant('{group}'));
Destination := ExpandConstant('{group}\{#IDFCmdExeShortcutFile}');
Description := '{#IDFCmdExeShortcutDescription}';
Command := ExpandConstant('/k {app}\idf_cmd_init.bat "') + PythonPath + '" "' + GitPath + '"';
Log('CreateShellLink Destination=' + Destination + ' Description=' + Description + ' Command=' + Command)
try
CreateShellLink(
Destination,
Description,
'cmd.exe',
Command,
GetIDFPath(''),
'', 0, SW_SHOWNORMAL);
except
MsgBox('Failed to create the Start menu shortcut: ' + Destination, mbError, MB_OK);
RaiseException('Failed to create the shortcut');
end;
end;

View File

@ -1,242 +1,97 @@
; Copyright 2019 Espressif Systems (Shanghai) PTE LTD
; SPDX-License-Identifier: Apache-2.0
#pragma include __INCLUDE__ + ";" + ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir")
#include <idp.iss> #include <idp.iss>
[Setup] #define MyAppName "ESP-IDF Tools"
AppName=ESP-IDF Tools #define MyAppVersion "2.0"
AppVersion=1.2 #define MyAppPublisher "Espressif Systems (Shanghai) Co. Ltd."
OutputBaseFilename=esp-idf-tools-setup-unsigned #define MyAppURL "https://github.com/espressif/esp-idf"
DefaultDirName={pf}\Espressif\ESP-IDF Tools #define PythonVersion "3.7"
DefaultGroupName=ESP-IDF Tools #define PythonInstallerName "python-3.7.3-amd64.exe"
Compression=lzma2 #define PythonInstallerDownloadURL "https://www.python.org/ftp/python/3.7.3/python-3.7.3-amd64.exe"
#define GitVersion "2.21.0"
#define GitInstallerName "Git-2.21.0-64-bit.exe"
#define GitInstallerDownloadURL "https://github.com/git-for-windows/git/releases/download/v2.21.0.windows.1/Git-2.21.0-64-bit.exe"
#define IDFVersionsURL "https://dl.espressif.com/dl/esp-idf/idf_versions.txt"
#define IDFCmdExeShortcutDescription "Open ESP-IDF Command Prompt (cmd.exe)"
#define IDFCmdExeShortcutFile "ESP-IDF Command Prompt (cmd.exe).lnk"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{9E068D99-5C4B-4E5F-96A3-B17CF291E6BD}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={%USERPROFILE}\.espressif
DirExistsWarning=no
DefaultGroupName=ESP-IDF
DisableProgramGroupPage=yes
OutputBaseFilename=esp-idf-tools-setup-unsigned
Compression=lzma
SolidCompression=yes SolidCompression=yes
ChangesEnvironment=yes
LicenseFile=license.txt
; Note: the rest of the installer setup is written to work cleanly on win32 also, *however*
; Ninja doesn't ship a 32-bit binary so there's no way yet to install on win32 :(
; See https://github.com/ninja-build/ninja/issues/1339
ArchitecturesAllowed=x64 ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64 ArchitecturesInstallIn64BitMode=x64
LicenseFile=license.txt
PrivilegesRequired=lowest
SetupLogging=yes
ChangesEnvironment=yes
WizardStyle=modern
[Types] [Languages]
Name: "full"; Description: "Default installation" Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "custom"; Description: "Custom installation"; Flags: iscustom
[Components] [Dirs]
Name: xtensa_esp32; Description: ESP32 Xtensa GCC Cross-Toolchain; Types: full custom; Name: "{app}\dist"
Name: mconf_idf; Description: ESP-IDF console menuconfig tool; Types: full custom;
Name: openocd_esp32; Description: openocd debug interface for ESP32; Types: full custom;
Name: esp32ulp_elf_binutils; Description: ULP binutils toolchain for ESP32; Types: full custom;
Name: ninja; Description: Install Ninja build v1.8.2; Types: full custom
[Tasks]
; Should installer prepend to Path (does this by default)
Name: addpath; Description: "Add tools to Path"; GroupDescription: "Add to Path:";
Name: addpath\allusers; Description: "For all users"; GroupDescription: "Add to Path:"; Flags: exclusive
Name: addpath\user; Description: "For the current user only"; GroupDescription: "Add to Path:"; Flags: exclusive unchecked
; External installation tasks
;
; Note: The Check conditions here auto-select 32-bit or 64-bit installers, as needed
; The tasks won't appear if CMake/Python27 already appear to be installed on this system
Name: cmake32; Description: Download and Run CMake 3.11.1 Installer; GroupDescription: "Other Required Tools:"; Check: not IsWin64 and not CMakeInstalled
Name: cmake64; Description: Download and Run CMake 3.11.1 Installer; GroupDescription: "Other Required Tools:"; Check: IsWin64 and not CMakeInstalled
Name: python32; Description: Download and Run Python 2.7.14 Installer and install Python dependencies; GroupDescription: "Other Required Tools:"; Check: not IsWin64 and not Python27Installed
Name: python64; Description: Download and Run Python 2.7.14 Installer and install Python dependencies; GroupDescription: "Other Required Tools:"; Check: IsWin64 and not Python27Installed
Name: python_requirements; Description: Install any missing Python dependencies; GroupDescription: "Other Required Tools:"; Check: Python27Installed
[Files] [Files]
Components: xtensa_esp32; Source: "input\xtensa-esp32-elf\*"; DestDir: "{app}\tools\"; Flags: recursesubdirs; Source: "cmdlinerunner\build\cmdlinerunner.dll"; Flags: dontcopy
Components: mconf_idf; Source: "input\mconf-v4.6.0.0-idf-20180525-win32\*"; DestDir: "{app}\mconf-idf\"; Source: "unzip\7za.exe"; Flags: dontcopy
Components: esp32ulp_elf_binutils; Source: "input\esp32ulp-elf-binutils\*"; DestDir: "{app}\tools\"; Flags: recursesubdirs; Source: "idf_versions.txt"; Flags: dontcopy
; Excludes for openocd are because some config files contain Cyrillic characters and inno can't encode them Source: "..\..\idf_tools.py"; DestDir: "{app}"; DestName: "idf_tools_fallback.py"
Components: openocd_esp32; Source: "input\openocd-esp32\*"; DestDir: "{app}\tools\"; Flags: recursesubdirs; Excludes: "target\1986*.cfg,target\*1879*.cfg" ; Note: this tools.json should match the requirements of IDF v3.x versions.
Components: ninja; Source: "input\ninja.exe"; DestDir: "{app}\tools\bin\"; ; For now, we use this copy to avoid duplication. Later we should create
Tasks: python32 python64 python_requirements; Source: "..\..\..\requirements.txt"; DestDir: "{tmp}"; Flags: deleteafterinstall; ; tools_fallback.json in this directory.
Source: "..\..\tools.json"; DestDir: "{app}"; DestName: "tools_fallback.json"
Source: "idf_cmd_init.bat"; DestDir: "{app}"
Source: "dist\*"; DestDir: "{app}\dist"
[UninstallDelete]
Type: filesandordirs; Name: "{app}\dist"
Type: filesandordirs; Name: "{app}\releases"
Type: filesandordirs; Name: "{app}\tools"
Type: filesandordirs; Name: "{app}\python_env"
[Run] [Run]
Tasks: cmake32 cmake64; Filename: "msiexec.exe"; Parameters: "/i ""{tmp}\cmake.msi"" /qb! {code:GetCMakeInstallerArgs}"; StatusMsg: Running CMake installer...; Filename: "{app}\dist\{#PythonInstallerName}"; Parameters: "/passive PrependPath=1 InstallLauncherAllUsers=0 Include_dev=0 Include_tcltk=0 Include_launcher=0 Include_test=0 Include_doc=0"; Description: "Installing Python"; Check: PythonInstallRequired
Tasks: python32 python64; Filename: "msiexec.exe"; Parameters: "/i ""{tmp}\python.msi"" /qb! {code:GetPythonInstallerArgs} REBOOT=Supress"; StatusMsg: Running Python installer...; Filename: "{app}\dist\{#GitInstallerName}"; Parameters: "/silent /tasks="""" /norestart"; Description: "Installing Git"; Check: GitInstallRequired
Tasks: python32 python64; Filename: "C:\Python27\Scripts\pip.exe"; Parameters: "install -r ""{tmp}\requirements.txt"""; StatusMsg: Installing Python modules...; Filename: "{group}\{#IDFCmdExeShortcutFile}"; Flags: postinstall shellexec; Description: "Run ESP-IDF Command Prompt (cmd.exe)"; Check: InstallationSuccessful
Tasks: python_requirements; Filename: "{code:Python27InstallPathInclude}\Scripts\pip.exe"; Parameters: "install -r ""{tmp}\requirements.txt"""; StatusMsg: Installing Python modules...;
[Registry] [Registry]
; Prepend various entries to Path in the registry. Can either be HKLM (all users) or HKCU (single user only) Root: HKCU; Subkey: "Environment"; ValueType: string; ValueName: "IDF_TOOLS_PATH"; \
ValueData: "{app}"; Flags: preservestringtype createvalueifdoesntexist;
; "tools" bin dir (ninja, xtensa & ULP toolchains, openocd all in this dir)
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{app}\tools\bin;{olddata}"; Check: not IsInPath('{app}'); \
Components: xtensa_esp32 esp32ulp_elf_binutils openocd_esp32 ninja; Tasks: addpath\allusers
Root: HKCU; Subkey: "Environment"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{app}\tools\bin;{olddata}"; Check: not IsInPath('{app}'); \
Components: xtensa_esp32 esp32ulp_elf_binutils openocd_esp32 ninja; Tasks: addpath\user
; mconf-idf path
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{app}\mconf-idf;{olddata}"; Check: not IsInPath('{app}\mconf-idf'); \
Components: mconf_idf; Tasks: addpath\allusers
Root: HKCU; Subkey: "Environment"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{app}\mconf-idf;{olddata}"; Check: not IsInPath('{app}\mconf-idf'); \
Components: mconf_idf; Tasks: addpath\user
; set OPENOCD_SCRIPTS environment variable
[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType:string; ValueName: "OPENOCD_SCRIPTS"; \
ValueData: "{app}\tools\share\openocd\scripts"; Flags: preservestringtype createvalueifdoesntexist; \
Components: openocd_esp32; Tasks: addpath\allusers
Root: HKCU; Subkey: "Environment"; ValueType:string; ValueName: "OPENOCD_SCRIPTS"; \
ValueData: "{app}\tools\share\openocd\scripts"; Flags: preservestringtype createvalueifdoesntexist; \
Components: openocd_esp32; Tasks: addpath\user
[Code] [Code]
procedure InitializeWizard;
begin
idpDownloadAfter(wpReady);
end;
procedure CurPageChanged(CurPageID: Integer);
begin
{ When the Ready page is being displayed, initialise downloads based on which Tasks are selected }
if CurPageID=wpReady then
begin
if IsTaskSelected('python32') then
begin
idpAddFile('https://www.python.org/ftp/python/2.7.14/python-2.7.14.msi', ExpandConstant('{tmp}\python.msi'));
end;
if IsTaskSelected('python64') then
begin
idpAddFile('https://www.python.org/ftp/python/2.7.14/python-2.7.14.amd64.msi', ExpandConstant('{tmp}\python.msi'));
end;
if IsTaskSelected('cmake32') then
begin
idpAddFile('https://cmake.org/files/v3.11/cmake-3.11.1-win32-x86.msi', ExpandConstant('{tmp}\cmake.msi'));
end;
if IsTaskSelected('cmake64') then
begin
idpAddFile('https://cmake.org/files/v3.11/cmake-3.11.1-win64-x64.msi', ExpandConstant('{tmp}\cmake.msi'));
end;
end;
end;
{ Utility to search in HKLM for an installation path. Looks in both 64-bit & 32-bit registry. }
function GetInstallPath(key, valuename : String) : Variant;
var
value : string;
begin
Result := Null;
if RegQueryStringValue(HKEY_LOCAL_MACHINE, key, valuename, value) then
begin
Result := value;
end
else
begin
{ This is 32-bit setup running on 64-bit Windows, but ESP-IDF can use 64-bit tools also }
if IsWin64 and RegQueryStringValue(HKLM64, key, valuename, value) then
begin
Result := value;
end;
end;
end;
{ Return the path of the CMake install, if there is one }
function CMakeInstallPath() : Variant;
begin
Result := GetInstallPath('SOFTWARE\Kitware\CMake', 'InstallDir');
end;
{ Return 'True' if CMake is installed }
function CMakeInstalled() : Boolean;
begin
Result := not VarIsNull(CMakeInstallPath());
end;
{ Return the path where Python 2.7 is installed, if there is one }
function Python27InstallPath() : Variant;
begin
Result := GetInstallPath('SOFTWARE\Python\PythonCore\2.7\InstallPath', '');
end;
{ Return the path where Python 2.7 is installed, suitable for including in code: tag }
function Python27InstallPathInclude(Ignored : String) : String;
begin
Result := Python27InstallPath();
end;
{ Return True if Python 2.7 is installed }
function Python27Installed() : Boolean;
begin
Result := not VarIsNull(Python27InstallPath());
end;
{ Return arguments to pass to CMake installer, ie should it add CMake to the Path }
function GetCMakeInstallerArgs(Param : String) : String;
begin
if IsTaskSelected('addpath\allusers') then
begin
Result := 'ADD_CMAKE_TO_PATH=System';
end
else if IsTaskSelected('addpath\user') then
begin
Result := 'ADD_CMAKE_TO_PATH=User';
end
else begin
Result := 'ADD_CMAKE_TO_PATH=None';
end;
end;
{ Return arguments to pass to the Python installer,
ie should it install for all users and should it prepend to the Path }
function GetPythonInstallerArgs(Param : String) : String;
begin
{ Note: The Python 2.7 installer appears to always add PATH to
system environment variables, regardless of ALLUSERS setting.
This appears to be fixed in the Python 3.x installers (which use WiX) }
if IsTaskSelected('addpath') then
begin
Result := 'ADDLOCAL=ALL ';
end
else begin
Result := ''
end;
if IsTaskSelected('addpath\allusers') then
begin
Result := Result + 'ALLUSERS=1';
end
else begin
Result := Result + 'ALLUSERS=';
end;
end;
{ Return True if the param is already set in the Path #include "utils.iss.inc"
(user or system, depending on which Task is chosen) #include "choice_page.iss.inc"
#include "cmdline_page.iss.inc"
Adapted from https://stackoverflow.com/a/3431379 #include "idf_page.iss.inc"
} #include "git_page.iss.inc"
function IsInPath(Param: string): boolean; #include "python_page.iss.inc"
var #include "idf_download_page.iss.inc"
OrigPath: string; #include "idf_setup.iss.inc"
RootKey : Integer; #include "summary.iss.inc"
SubKey : String; #include "main.iss.inc"
begin
if IsTaskSelected('addpath\allusers') then
begin
RootKey := HKEY_LOCAL_MACHINE;
SubKey := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
end
else begin
RootKey := HKEY_CURRENT_USER;
SubKey := 'Environment';
end;
if not RegQueryStringValue(RootKey, SubKey, 'Path', OrigPath)
then begin
Result := False;
end
else begin
{ look for the path with leading and trailing semicolon }
Result := Pos(';' + Param + ';', ';' + OrigPath + ';') > 0;
end;
end;

View File

@ -0,0 +1,121 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Custom steps before the main installation flow ------------------------------ }
var
SetupAborted: Boolean;
function InstallationSuccessful(): Boolean;
begin
Result := not SetupAborted;
end;
<event('InitializeWizard')>
procedure InitializeDownloader();
begin
idpDownloadAfter(wpReady);
end;
<event('NextButtonClick')>
function PreInstallSteps(CurPageID: Integer): Boolean;
var
DestPath: String;
begin
Result := True;
if CurPageID <> wpReady then
exit;
ForceDirectories(ExpandConstant('{app}\dist'));
if not PythonUseExisting then
begin
DestPath := ExpandConstant('{app}\dist\{#PythonInstallerName}');
if FileExists(DestPath) then
begin
Log('Python installer already downloaded: ' + DestPath);
end else begin
idpAddFile('{#PythonInstallerDownloadURL}', DestPath);
end;
end;
if not GitUseExisting then
begin
DestPath := ExpandConstant('{app}\dist\{#GitInstallerName}');
if FileExists(DestPath) then
begin
Log('Git installer already downloaded: ' + DestPath);
end else begin
idpAddFile('{#GitInstallerDownloadURL}', DestPath);
end;
end;
if not IDFUseExisting then
begin
IDFAddDownload();
end;
end;
{ ------------------------------ Custom steps after the main installation flow ------------------------------ }
procedure AddPythonGitToPath();
var
EnvPath: String;
PythonLibPath: String;
begin
EnvPath := GetEnv('PATH');
if not PythonUseExisting then
PythonExecutablePathUpdateAfterInstall();
if not GitUseExisting then
GitExecutablePathUpdateAfterInstall();
EnvPath := PythonPath + ';' + GitPath + ';' + EnvPath;
Log('Setting PATH for this process: ' + EnvPath);
SetEnvironmentVariable('PATH', EnvPath);
{ Log and clear PYTHONPATH variable, as it might point to libraries of another Python version}
PythonLibPath := GetEnv('PYTHONPATH')
Log('PYTHONPATH=' + PythonLibPath)
SetEnvironmentVariable('PYTHONPATH', '')
end;
<event('CurStepChanged')>
procedure PostInstallSteps(CurStep: TSetupStep);
var
Err: Integer;
begin
if CurStep <> ssPostInstall then
exit;
try
AddPythonGitToPath();
if not IDFUseExisting then
IDFDownload();
IDFToolsSetup();
CreateIDFCommandPromptShortcut();
except
SetupAborted := True;
if MsgBox('Installation log has been created, it may contain more information about the problem.' + #13#10
+ 'Display the installation log now?', mbConfirmation, MB_YESNO or MB_DEFBUTTON1) = IDYES then
begin
ShellExec('', 'notepad.exe', ExpandConstant('{log}'), ExpandConstant('{tmp}'), SW_SHOW, ewNoWait, Err);
end;
Abort();
end;
end;
<event('ShouldSkipPage')>
function SkipFinishedPage(PageID: Integer): Boolean;
begin
Result := False;
if PageID = wpFinished then
begin
Result := SetupAborted;
end;
end;

View File

@ -0,0 +1,113 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Find installed Python interpreters in Windows Registry (see PEP 514) ------------------------------ }
var
InstalledPythonVersions: TStringList;
InstalledPythonDisplayNames: TStringList;
InstalledPythonExecutables: TStringList;
procedure PythonVersionAdd(Version, DisplayName, Executable: String);
begin
Log('Adding Python version=' + Version + ' name='+DisplayName+' executable='+Executable);
InstalledPythonVersions.Append(Version);
InstalledPythonDisplayNames.Append(DisplayName);
InstalledPythonExecutables.Append(Executable);
end;
function GetPythonVersionInfoFromKey(RootKey: Integer; SubKeyName, CompanyName, TagName: String;
var Version: String;
var DisplayName: String;
var ExecutablePath: String): Boolean;
var
TagKey, InstallPathKey, DefaultPath: String;
begin
TagKey := SubKeyName + '\' + CompanyName + '\' + TagName;
InstallPathKey := TagKey + '\InstallPath';
if not RegQueryStringValue(RootKey, InstallPathKey, '', DefaultPath) then
begin
Log('No (Default) key, skipping');
Result := False;
exit;
end;
if not RegQueryStringValue(RootKey, InstallPathKey, 'ExecutablePath', ExecutablePath) then
begin
Log('No ExecutablePath, using the default');
ExecutablePath := DefaultPath + '\python.exe';
end;
if not RegQueryStringValue(RootKey, TagKey, 'SysVersion', Version) then
begin
if CompanyName = 'PythonCore' then
begin
Version := TagName;
Delete(Version, 4, Length(Version));
end else begin
Log('Can not determine SysVersion');
Result := False;
exit;
end;
end;
if not RegQueryStringValue(RootKey, TagKey, 'DisplayName', DisplayName) then
begin
DisplayName := 'Python ' + Version;
end;
Result := True;
end;
procedure FindPythonVersionsFromKey(RootKey: Integer; SubKeyName: String);
var
CompanyNames: TArrayOfString;
CompanyName, CompanySubKey, TagName, TagSubKey: String;
ExecutablePath, DisplayName, Version: String;
TagNames: TArrayOfString;
CompanyId, TagId: Integer;
begin
if not RegGetSubkeyNames(RootKey, SubKeyName, CompanyNames) then
begin
Log('Nothing found in ' + IntToStr(RootKey) + '\' + SubKeyName);
Exit;
end;
for CompanyId := 0 to GetArrayLength(CompanyNames) - 1 do
begin
CompanyName := CompanyNames[CompanyId];
if CompanyName = 'PyLauncher' then
continue;
CompanySubKey := SubKeyName + '\' + CompanyName;
Log('In ' + IntToStr(RootKey) + '\' + CompanySubKey);
if not RegGetSubkeyNames(RootKey, CompanySubKey, TagNames) then
continue;
for TagId := 0 to GetArrayLength(TagNames) - 1 do
begin
TagName := TagNames[TagId];
TagSubKey := CompanySubKey + '\' + TagName;
Log('In ' + IntToStr(RootKey) + '\' + TagSubKey);
if not GetPythonVersionInfoFromKey(RootKey, SubKeyName, CompanyName, TagName, Version, DisplayName, ExecutablePath) then
continue;
PythonVersionAdd(Version, DisplayName, ExecutablePath);
end;
end;
end;
procedure FindInstalledPythonVersions();
begin
InstalledPythonVersions := TStringList.Create();
InstalledPythonDisplayNames := TStringList.Create();
InstalledPythonExecutables := TStringList.Create();
FindPythonVersionsFromKey(HKEY_CURRENT_USER, 'Software\Python');
FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Python');
FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Wow6432Node\Python');
end;

View File

@ -0,0 +1,149 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select Python interpreter ------------------------------ }
#include "python_find_installed.iss.inc"
var
PythonPage: TInputOptionWizardPage;
PythonVersion, PythonPath, PythonExecutablePath: String;
PythonUseExisting: Boolean;
function GetPythonPath(Unused: String): String;
begin
Result := PythonPath;
end;
function PythonInstallRequired(): Boolean;
begin
Result := not PythonUseExisting;
end;
function PythonVersionSupported(Version: String): Boolean;
var
Major, Minor: Integer;
begin
Result := False;
if not VersionExtractMajorMinor(Version, Major, Minor) then
begin
Log('PythonVersionSupported: Could not parse version=' + Version);
exit;
end;
if (Major = 2) and (Minor = 7) then Result := True;
if (Major = 3) and (Minor >= 5) then Result := True;
end;
procedure OnPythonPagePrepare(Sender: TObject);
var
Page: TInputOptionWizardPage;
FullName: String;
i, Index, FirstEnabledIndex: Integer;
OfferToInstall: Boolean;
VersionToInstall: String;
VersionSupported: Boolean;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnPythonPagePrepare');
if Page.CheckListBox.Items.Count > 0 then
exit;
FindInstalledPythonVersions();
VersionToInstall := '{#PythonVersion}';
OfferToInstall := True;
FirstEnabledIndex := -1;
for i := 0 to InstalledPythonVersions.Count - 1 do
begin
VersionSupported := PythonVersionSupported(InstalledPythonVersions[i]);
FullName := InstalledPythonDisplayNames.Strings[i];
if not VersionSupported then
begin
FullName := FullName + ' (unsupported)';
end;
FullName := FullName + #13#10 + InstalledPythonExecutables.Strings[i];
Index := Page.Add(FullName);
if not VersionSupported then
begin
Page.CheckListBox.ItemEnabled[Index] := False;
end else begin
if FirstEnabledIndex < 0 then FirstEnabledIndex := Index;
end;
if InstalledPythonVersions[i] = VersionToInstall then
begin
OfferToInstall := False;
end;
end;
if OfferToInstall then
begin
Index := Page.Add('Install Python ' + VersionToInstall);
if FirstEnabledIndex < 0 then FirstEnabledIndex := Index;
end;
Page.SelectedValueIndex := FirstEnabledIndex;
end;
procedure OnPythonSelectionChange(Sender: TObject);
var
Page: TInputOptionWizardPage;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnPythonSelectionChange index=' + IntToStr(Page.SelectedValueIndex));
end;
function OnPythonPageValidate(Sender: TWizardPage): Boolean;
var
Page: TInputOptionWizardPage;
begin
Page := TInputOptionWizardPage(Sender);
Log('OnPythonPageValidate index=' + IntToStr(Page.SelectedValueIndex));
if Page.SelectedValueIndex < InstalledPythonExecutables.Count then
begin
PythonUseExisting := True;
PythonExecutablePath := InstalledPythonExecutables[Page.SelectedValueIndex];
PythonPath := ExtractFilePath(PythonExecutablePath);
PythonVersion := InstalledPythonVersions[Page.SelectedValueIndex];
end else begin
PythonUseExisting := False;
PythonExecutablePath := '';
PythonPath := '';
PythonVersion := '{#PythonVersion}';
end;
Log('OnPythonPageValidate: PythonPath='+PythonPath+' PythonExecutablePath='+PythonExecutablePath);
Result := True;
end;
procedure PythonExecutablePathUpdateAfterInstall();
var
Version, DisplayName, ExecutablePath: String;
begin
if not GetPythonVersionInfoFromKey(
HKEY_CURRENT_USER, 'Software\Python', 'PythonCore', '{#PythonVersion}',
Version, DisplayName, ExecutablePath) then
begin
Log('Failed to find ExecutablePath for the installed copy of Python');
exit;
end;
Log('Found ExecutablePath for ' + DisplayName + ': ' + ExecutablePath);
PythonExecutablePath := ExecutablePath;
PythonPath := ExtractFilePath(PythonExecutablePath);
Log('PythonExecutablePathUpdateAfterInstall: PythonPath='+PythonPath+' PythonExecutablePath='+PythonExecutablePath);
end;
<event('InitializeWizard')>
procedure CreatePythonPage();
begin
PythonPage := ChoicePageCreate(
wpLicense,
'Python choice', 'Please choose Python version',
'Available Python versions',
'',
False,
@OnPythonPagePrepare,
@OnPythonSelectionChange,
@OnPythonPageValidate);
end;

View File

@ -0,0 +1,40 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Installation summary page ------------------------------ }
function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo,
MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
begin
Result := ''
if PythonUseExisting then
begin
Result := Result + 'Using Python ' + PythonVersion + ':' + NewLine
+ Space + PythonExecutablePath + NewLine + NewLine;
end else begin
Result := Result + 'Will download and install Python ' + PythonVersion + NewLine + NewLine;
end;
if GitUseExisting then
begin
Result := Result + 'Using Git ' + GitVersion + ':' + NewLine
+ Space + GitExecutablePath + NewLine + NewLine;
end else begin
Result := Result + 'Will download and install Git for Windows ' + GitVersion + NewLine + NewLine;
end;
if IDFUseExisting then
begin
Result := Result + 'Using existing ESP-IDF copy: ' + NewLine
+ Space + IDFExistingPath + NewLine + NewLine;
end else begin
Result := Result + 'Will download ESP-IDF ' + IDFDownloadVersion + ' into:' + NewLine
+ Space + IDFDownloadPath + NewLine + NewLine;
end;
Result := Result + 'IDF tools directory (IDF_TOOLS_PATH):' + NewLine +
Space + ExpandConstant('{app}') + NewLine + NewLine;
Log('Summary message: ' + NewLine + Result);
end;

View File

@ -0,0 +1,157 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Helper functions from libcmdlinerunner.dll ------------------------------ }
function ProcStart(cmdline, workdir: string): Longword;
external 'proc_start@files:cmdlinerunner.dll cdecl';
function ProcGetExitCode(inst: Longword): DWORD;
external 'proc_get_exit_code@files:cmdlinerunner.dll cdecl';
function ProcGetOutput(inst: Longword; dest: PAnsiChar; sz: DWORD): DWORD;
external 'proc_get_output@files:cmdlinerunner.dll cdecl';
procedure ProcEnd(inst: Longword);
external 'proc_end@files:cmdlinerunner.dll cdecl';
{ ------------------------------ WinAPI functions ------------------------------ }
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function SetEnvironmentVariable(lpName: string; lpValue: string): BOOL;
external 'SetEnvironmentVariable{#AW}@kernel32.dll stdcall';
{ ------------------------------ Functions to query the registry ------------------------------ }
{ Utility to search in HKLM and HKCU for an installation path. Looks in both 64-bit & 32-bit registry. }
function GetInstallPath(key, valuename : String) : String;
var
value: String;
begin
Result := '';
if RegQueryStringValue(HKEY_LOCAL_MACHINE, key, valuename, value) then
begin
Result := value;
exit;
end;
if RegQueryStringValue(HKEY_CURRENT_USER, key, valuename, value) then
begin
Result := value;
exit;
end;
{ This is 32-bit setup running on 64-bit Windows, but ESP-IDF can use 64-bit tools also }
if IsWin64 and RegQueryStringValue(HKLM64, key, valuename, value) then
begin
Result := value;
exit;
end;
if IsWin64 and RegQueryStringValue(HKCU64, key, valuename, value) then
begin
Result := value;
exit;
end;
end;
{ ------------------------------ Function to exit from the installer ------------------------------ }
procedure AbortInstallation(Message: String);
begin
MsgBox(Message, mbError, MB_OK);
Abort();
end;
{ ------------------------------ File system related functions ------------------------------ }
function DirIsEmpty(DirName: String): Boolean;
var
FindRec: TFindRec;
begin
Result := True;
if FindFirst(DirName+'\*', FindRec) then begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then begin
Result := False;
break;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
type
TFindFileCallback = procedure(Filename: String);
procedure FindFileRecusive(Directory: string; FileName: string; Callback: TFindFileCallback);
var
FindRec: TFindRec;
FilePath: string;
begin
if FindFirst(Directory + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name = '.') or (FindRec.Name = '..') then
continue;
FilePath := Directory + '\' + FindRec.Name;
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
begin
FindFileRecusive(FilePath, FileName, Callback);
end else if CompareText(FindRec.Name, FileName) = 0 then
begin
Callback(FilePath);
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
{ ------------------------------ Version related functions ------------------------------ }
function VersionExtractMajorMinor(Version: String; var Major: Integer; var Minor: Integer): Boolean;
var
Delim: Integer;
MajorStr, MinorStr: String;
OrigVersion, ExpectedPrefix: String;
begin
Result := False;
OrigVersion := Version;
Delim := Pos('.', Version);
if Delim = 0 then exit;
MajorStr := Version;
Delete(MajorStr, Delim, Length(MajorStr));
Delete(Version, 1, Delim);
Major := StrToInt(MajorStr);
Delim := Pos('.', Version);
if Delim = 0 then Delim := Length(MinorStr);
MinorStr := Version;
Delete(MinorStr, Delim, Length(MinorStr));
Delete(Version, 1, Delim);
Minor := StrToInt(MinorStr);
{ Sanity check }
ExpectedPrefix := IntToStr(Major) + '.' + IntToStr(Minor);
if Pos(ExpectedPrefix, OrigVersion) <> 1 then
begin
Log('VersionExtractMajorMinor: version=' + OrigVersion + ', expected=' + ExpectedPrefix);
exit;
end;
Result := True;
end;