diff --git a/Internal/os_clang_format_style_file b/Internal/os_clang_format_style_file new file mode 100644 index 0000000..4e5604a --- /dev/null +++ b/Internal/os_clang_format_style_file @@ -0,0 +1,459 @@ +--- +IndentWidth: 4 +TabWidth: 4 +--- +Language: Cpp + +# Align parameters on the open bracket, e.g.: +# someLongFunction(argument1, +# argument2); +AlignAfterOpenBracket: Align + +# Align array column and left justify the columns e.g.: +# struct test demo[] = +# { +# {56, 23, "hello"}, +# {-1, 93463, "world"}, +# {7, 5, "!!" } +# }; +AlignArrayOfStructures: Left + +# Align assignments on consecutive lines. This will result in formattings like: +# +# int a = 1; +# int somelongname = 2; +# double c = 3; +# +# int d = 3; +# /* A comment. */ +# double e = 4; +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveBitFields: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignConsecutiveMacros: Consecutive + +# Align escaped newlines as far left as possible. +# #define A \ +# int aaaa; \ +# int b; \ +# int dddddddddd; +AlignEscapedNewlines: Left + +# Horizontally align operands of binary and ternary expressions. +# Specifically, this aligns operands of a single expression that needs to be +# split over multiple lines, e.g.: +# +# int aaa = bbbbbbbbbbbbbbb + +# ccccccccccccccc; +AlignOperands: Align + +# true: false: +# int a; // My comment a vs. int a; // My comment a +# int b = 2; // comment b int b = 2; // comment about b +AlignTrailingComments: true + +# If the function declaration doesn’t fit on a line, allow putting all +# parameters of a function declaration onto the next line even if +# BinPackParameters is false. +# +# true: +# void myFunction( +# int a, int b, int c, int d, int e); +# +# false: +# void myFunction(int a, +# int b, +# int c, +# int d, +# int e); +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never # "while (true) { continue; }" can be put on a single line. + +# If true, short case labels will be contracted to a single line. +# +# true: false: +# switch (a) { vs. switch (a) { +# case 1: x = 1; break; case 1: +# case 2: return; x = 1; +# } break; +# case 2: +# return; +# } +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: true # enum { A, B } myEnum; + +# Only merge functions defined inside a class. Implies “empty”. +# +# class Foo { +# void f() { foo(); } +# }; +# void f() { +# foo(); +# } +# void f() {} +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false + +# Only merge empty lambdas. +# +# auto lambda = [](int a) {} +# auto lambda2 = [](int a) { +# return a; +# }; +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false + +# true: false: +# aaaa = vs. aaaa = "bbbb" +# "bbbb" "cccc"; +# "cccc"; +AlwaysBreakBeforeMultilineStrings: true + +# Force break after template declaration only when the following declaration +# spans multiple lines. +# +# template T foo() { +# } +# template +# T foo(int aaaaaaaaaaaaaaaaaaaaa, +# int bbbbbbbbbbbbbbbbbbbbb) { +# } +AlwaysBreakTemplateDeclarations: MultiLine + +# If false, a function call’s arguments will either be all on the same line or +# will have one line each. +# +# true: +# void f() { +# f(aaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaa, +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa); +# } +# +# false: +# void f() { +# f(aaaaaaaaaaaaaaaaaaaa, +# aaaaaaaaaaaaaaaaaaaa, +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa); +# } +BinPackArguments: false +BinPackParameters: false # As BinPackArguments but for function definition parameters + +# Add space after the : only (space may be added before if needed for +# AlignConsecutiveBitFields). +# +# unsigned bf: 2; +BitFieldColonSpacing: After + +# LooooooooooongType loooooooooooooooooooooongVariable = +# someLooooooooooooooooongFunction(); +# +# bool value = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && +# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa > +# ccccccccccccccccccccccccccccccccccccccccc; +BreakBeforeBinaryOperators: None + +# Always attach braces to surrounding context, but break before braces on +# function, namespace and class definitions. +BreakBeforeBraces: Linux + +# true: +# template +# concept ... +# +# false: +# template concept ... +BreakBeforeConceptDeclarations: false + +# true: +# veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongDescription +# ? firstValue +# : SecondValueVeryVeryVeryVeryLong; +# +# false: +# veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongDescription ? +# firstValue : +# SecondValueVeryVeryVeryVeryLong; +BreakBeforeTernaryOperators: true + +# Break constructor initializers before the colon and commas, and align the +# commas with the colon. +# +# Constructor() +# : initializer1() +# , initializer2() +BreakConstructorInitializers: BeforeComma + +# Break inheritance list only after the commas. +# +# class Foo : Base1, +# Base2 +# {}; +BreakInheritanceList: AfterComma + +# true: +# const char* x = "veryVeryVeryVeryVeryVe" +# "ryVeryVeryVeryVeryVery" +# "VeryLongString"; +BreakStringLiterals: true +ColumnLimit: 100 + +# false: +# namespace Foo { +# namespace Bar { +# } +# } +CompactNamespaces: false + +# true: false: +# vector x{1, 2, 3, 4}; vs. vector x{ 1, 2, 3, 4 }; +# vector x{{}, {}, {}, {}}; vector x{ {}, {}, {}, {} }; +# f(MyMap[{composite, key}]); f(MyMap[{ composite, key }]); +# new int[3]{1, 2, 3}; new int[3]{ 1, 2, 3 }; +Cpp11BracedListStyle: true + +# Analyze the formatted file for the most used line ending (\r\n or \n). UseCRLF +# is only used as a fallback if none can be derived. +DeriveLineEnding: true +DerivePointerAlignment: true # As per DeriveLineEnding except for pointers and references + +# Add empty line only when access modifier starts a new logical block. Logical +# block is a group of one or more member fields or functions. +# +# struct foo { +# private: +# int i; +# +# protected: +# int j; +# /* comment */ +# public: +# foo() {} +# +# private: +# protected: +# }; +EmptyLineBeforeAccessModifier: LogicalBlock + +# true: false: +# namespace a { vs. namespace a { +# foo(); foo(); +# bar(); bar(); +# } // namespace a } +FixNamespaceComments: true + +# false: true: +# class C { vs. class C { +# class D { class D { +# void bar(); void bar(); +# protected: protected: +# D(); D(); +# }; }; +# public: public: +# C(); C(); +# }; }; +# void foo() { void foo() { +# return 1; return 1; +# } } +IndentAccessModifiers: false + +# false: true: +# switch (fool) { vs. switch (fool) { +# case 1: { case 1: +# bar(); { +# } break; bar(); +# default: { } +# plop(); break; +# } default: +# } { +# plop(); +# } +# } +IndentCaseBlocks: false + +# false: true: +# switch (fool) { vs. switch (fool) { +# case 1: case 1: +# bar(); bar(); +# break; break; +# default: default: +# plop(); plop(); +# } } +IndentCaseLabels: true + +# extern "C" { +# void foo(); +# } +IndentExternBlock: NoIndent + +# Indents directives before the hash. +# +# #if FOO +# #if BAR +# #include +# #endif +# #endif +IndentPPDirectives: BeforeHash + +# true: false: +# if (foo) { vs. if (foo) { +# bar(); +# bar(); } +# } +KeepEmptyLinesAtTheStartOfBlocks: false + +# The maximum number of consecutive empty lines to keep. +# +# MaxEmptyLinesToKeep: 1 vs. MaxEmptyLinesToKeep: 0 +# int f() { int f() { +# int = 1; int i = 1; +# i = foo(); +# i = foo(); return i; +# } +# return i; +# } +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None + +# Put all constructor initializers on the current line if they fit. Otherwise, +# put each one on its own line. +# +# Constructor() : a(), b() +# +# Constructor() +# : aaaaaaaaaaaaaaaaaaaa(), +# bbbbbbbbbbbbbbbbbbbb(), +# ddddddddddddd() +PackConstructorInitializers: CurrentLine +PointerAlignment: Right + +# false: +# // veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of information +# /* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of information */ +# +# true: +# // veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of +# // information +# /* second veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongComment with plenty of +# * information */ +ReflowComments: true + +# false: true: +# +# if (isa(D)) { vs. if (isa(D)) +# handleFunctionDecl(D); handleFunctionDecl(D); +# } else if (isa(D)) { else if (isa(D)) +# handleVarDecl(D); handleVarDecl(D); +# } +# +# if (isa(D)) { vs. if (isa(D)) { +# for (auto *A : D.attrs()) { for (auto *A : D.attrs()) +# if (shouldProcessAttr(A)) { if (shouldProcessAttr(A)) +# handleAttr(A); handleAttr(A); +# } } +# } +# } +# +# if (isa(D)) { vs. if (isa(D)) +# for (auto *A : D.attrs()) { for (auto *A : D.attrs()) +# handleAttr(A); handleAttr(A); +# } +# } +# +# if (auto *D = (T)(D)) { vs. if (auto *D = (T)(D)) { +# if (shouldProcess(D)) { if (shouldProcess(D)) +# handleVarDecl(D); handleVarDecl(D); +# } else { else +# markAsIgnored(D); markAsIgnored(D); +# } } +# } +# +# if (a) { vs. if (a) +# b(); b(); +# } else { else if (c) +# if (c) { d(); +# d(); else +# } else { e(); +# e(); +# } +# } +RemoveBracesLLVM: true + +# Never v.s. Always +# #include #include +# struct Foo { +# int a, b, c; struct Foo { +# }; int a, b, c; +# namespace Ns { }; +# class Bar { +# public: namespace Ns { +# struct Foobar { class Bar { +# int a; public: +# int b; struct Foobar { +# }; int a; +# private: int b; +# int t; }; +# int method1() { +# // ... private: +# } int t; +# enum List { +# ITEM1, int method1() { +# ITEM2 // ... +# }; } +# template +# int method2(T x) { enum List { +# // ... ITEM1, +# } ITEM2 +# int i, j, k; }; +# int method3(int par) { +# // ... template +# } int method2(T x) { +# }; // ... +# class C {}; } +# } +# int i, j, k; +# +# int method3(int par) { +# // ... +# } +# }; +# +# class C {}; +# } +SeparateDefinitionBlocks: Always + +# true: false: +# int a = 5; vs. int a= 5; +# a += 42; a+= 42; +SpaceBeforeAssignmentOperators: true + +# Put a space before opening parentheses only after control statement keywords +# (for/if/while...). +# +# void f() { +# if (true) { +# f(); +# } +# } +SpaceBeforeParens: ControlStatements +SpacesBeforeTrailingComments: 1 + +# static_cast(arg); +# std::function fct; +SpacesInAngles: Never + +Standard: Auto + +# Macros which are ignored in front of a statement, as if they were an +# attribute. So that they are not parsed as identifier, for example for Qts +# emit. +# unsigned char data = 'x'; +# emit signal(data); // This is parsed as variable declaration. +# +# vs. +# +# unsigned char data = 'x'; +# emit signal(data); // Now it's fine again. +StatementAttributeLikeMacros: [emit] +--- diff --git a/Internal/os_nvim_init.vim b/Internal/os_nvim_init.vim new file mode 100644 index 0000000..dab42f2 --- /dev/null +++ b/Internal/os_nvim_init.vim @@ -0,0 +1,377 @@ +" Plugins +" ============================================================================== +call plug#begin(stdpath('config') . '/plugged') + " nerdtree provides a file tree explorer + " vim-dispatch allows running async jobs in vim (i.e. builds in the background) + Plug 'https://github.com/scrooloose/nerdtree', { 'on': 'NERDTreeToggle' } + Plug 'https://github.com/tpope/vim-dispatch' + Plug 'https://github.com/tpope/vim-fugitive' + Plug 'https://github.com/tpope/vim-abolish' + + " TODO: 2022-06-19 Treesitter is too slow on large C++ files + " Plug 'https://github.com/nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'} + Plug 'https://github.com/bfrg/vim-cpp-modern' + + " FZF + Plug 'junegunn/fzf' + Plug 'junegunn/fzf.vim' + + " FZF for LSP + Plug 'gfanto/fzf-lsp.nvim' + Plug 'nvim-lua/plenary.nvim' + + " odin for syntax highlighting + Plug 'https://github.com/Tetralux/odin.vim' + Plug 'https://github.com/sainnhe/gruvbox-material' + + " Lua cache to speed up load times + Plug 'https://github.com/lewis6991/impatient.nvim' + + " lsp-zero begin + " LSP Support + Plug 'neovim/nvim-lspconfig' + Plug 'williamboman/mason.nvim' + Plug 'williamboman/mason-lspconfig.nvim' + + " Autocompletion + Plug 'hrsh7th/nvim-cmp' + Plug 'hrsh7th/cmp-buffer' + Plug 'hrsh7th/cmp-path' + Plug 'saadparwaiz1/cmp_luasnip' + Plug 'hrsh7th/cmp-nvim-lsp' + Plug 'hrsh7th/cmp-nvim-lua' + + " Snippets + Plug 'L3MON4D3/LuaSnip' + + " Snippet collection (Optional) + Plug 'rafamadriz/friendly-snippets' + Plug 'VonHeikemen/lsp-zero.nvim' + " lsp-zero end +call plug#end() + +" Lua Setup +" ============================================================================== +lua <-,trail:■,extends:»,precedes:«') + vim.opt.number=true -- Show line numbers + vim.opt.relativenumber=true -- Show relative line numbers + vim.opt.shiftwidth=4 -- Number of spaces for each autoindent step + vim.opt.splitright=true -- Open new splits to the right of the current one + vim.opt.swapfile=false -- Disable swapfile (stores the things changed in a file) + vim.opt.textwidth=80 -- On format, format to 80 char long lines + vim.opt.visualbell=true -- Flash the screen on error + vim.opt.wrap=false -- Don't wrap lines of text automatically + vim.opt.signcolumn = 'no' + + vim.diagnostic.config({ + -- Turn off the diagnostics signs on the line number. In LSP mode, editing + -- a C++ buffer constantly toggles the sign column on and off as you change + -- modes which is very visually distracting. + signs = false, + }) + + -- Check if there were args (i.e. opened file), non-empty buffer, or started in insert mode + if vim.fn.argc() == 0 or vim.fn.line2byte("$") ~= -1 and not opt.insertmode then + local ascii = { + "", + " Useful Bindings (Normal Mode)", + " --------------------------------------------------", + " to open the file tree explorer", + " clang format selected lines", + " jump to next compilation error", + " jump to prev compilation error", + " change working directory to current file", + " <\\s> split buffer vertically", + "", + " Abolish (Text Substitution in Normal Mode)", + " --------------------------------------------------", + " %S/facilit{y,ies}/building{,s}/g Convert facility->building, facilities->buildings", + " %S/action/sleep/g Convert action to sleep, (preserve case sensitivity ACTION->SLEEP, action->sleep) ", + "", + " FZF (Normal Mode)", + " --------------------------------------------------", + " <\\h> vim command history", + " <\\f> find files", + " <\\g> search for text (via ripgrep)", + " <\\tt> search for tag (global)", + " <\\tb> search for tag (buffer)", + " <\\cc> search for commit (global)", + " <\\cb> search for commit (buffer)", + " <\\b> search for buffer", + "", + " Autocompletion (nvim-cmp in Normal Mode)", + " --------------------------------------------------", + " Confirms selection.", + " Confirms selection.", + " Navigate to previous item on the list.", + " Navigate to the next item on the list.", + " Navigate to previous item on the list.", + " Navigate to the next item on the list.", + " Scroll up in the item's documentation.", + " Scroll down in the item's documentation.", + " Toggles the completion.", + " Go to the next placeholder in the snippet.", + " Go to the previous placeholder in the snippet.", + " Enables completion when the cursor is inside a word. If the completion menu is visible it will navigate to the next item in the list.", + " When the completion menu is visible navigate to the previous item in the list.", + "", + " LSP Bindings (Normal Mode)", + " --------------------------------------------------", + " Displays hover information about the symbol under the cursor in a floating window. See help vim.lsp.buf.hover().", + " gd Jumps to the definition of the symbol under the cursor. See help vim.lsp.buf.definition().", + " gD Jumps to the declaration of the symbol under the cursor. Some servers don't implement this feature. See help vim.lsp.buf.declaration().", + " gi Lists all the implementations for the symbol under the cursor in the quickfix window. See help vim.lsp.buf.implementation().", + " go Jumps to the definition of the type of the symbol under the cursor. See help vim.lsp.buf.type_definition().", + " gr Lists all the references to the symbol under the cursor in the quickfix window. See help vim.lsp.buf.references().", + " Displays signature information about the symbol under the cursor in a floating window. See help vim.lsp.buf.signature_help(). If a mapping already exists for this key this function is not bound.", + " Renames all references to the symbol under the cursor. See help vim.lsp.buf.rename().", + " Selects a code action available at the current cursor position. See help vim.lsp.buf.code_action().", + " gl Show diagnostics in a floating window. See :help vim.diagnostic.open_float().", + " [d Move to the previous diagnostic in the current buffer. See :help vim.diagnostic.goto_prev().", + " ]d Move to the next diagnostic. See :help vim.diagnostic.goto_next()." + } + + local height = vim.api.nvim_get_option("lines") + local width = vim.api.nvim_get_option("columns") + local ascii_rows = #ascii + local ascii_cols = #ascii[1] + local win = vim.api.nvim_get_current_win() + local buf = vim.api.nvim_create_buf(true, true) + + local function reset_start_screen() + vim.cmd("enew") + local buf = vim.api.nvim_get_current_buf() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_buf_set_option(buf, "modifiable", true) + vim.api.nvim_buf_set_option(buf, "buflisted", true) + vim.api.nvim_buf_set_option(buf, "buflisted", true) + end + + vim.api.nvim_buf_set_lines(buf, 0, -1, false, ascii) + vim.api.nvim_buf_set_option(buf, "modified", false) + vim.api.nvim_buf_set_option(buf, "buflisted", false) + vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe") + vim.api.nvim_buf_set_option(buf, "buftype", "nofile") + vim.api.nvim_buf_set_option(buf, "swapfile", false) + vim.api.nvim_set_current_buf(buf) + + vim.api.nvim_create_autocmd("InsertEnter,WinEnter", { + pattern = "", + callback = reset_start_screen, + }) + end + +EOF + +" Theme +" ============================================================================== +let g:gruvbox_material_background='hard' +let g:gruvbox_material_foreground='mix' +let g:gruvbox_material_disable_italic_comment=1 +let g:gruvbox_material_enable_italic=0 +let g:gruvbox_material_enable_bold=0 +let g:gruvbox_material_diagnostic_virtual_text='colored' +let g:gruvbox_material_better_performance=1 +colorscheme gruvbox-material + +" Vim-cpp-modern customisation +" Disable function highlighting (affects both C and C++ files) +let g:cpp_function_highlight = 1 + +" Enable highlighting of C++11 attributes +let g:cpp_attributes_highlight = 1 + +" Highlight struct/class member variables (affects both C and C++ files) +let g:cpp_member_highlight = 0 + +" Put all standard C and C++ keywords under Vim's highlight group 'Statement' +" (affects both C and C++ files) +let g:cpp_simple_highlight = 1 + +" Options +" ============================================================================== +" Show EOL type and last modified timestamp, right after the filename +set statusline=%<%F%h%m%r\ [%{&ff}]\ (%{strftime(\"%H:%M\ %d/%m/%Y\",getftime(expand(\"%:p\")))})%=%l,%c%V\ %P + +" Resize splits when the window is resized +au VimResized * :wincmd = + +" File patterns to ignore in command line auto complete +set wildignore+=*.class,*.o +set wildignore+=*\\tmp\\*,*.swp,*.zip,*.exe,*.obj,*.vcxproj,*.pdb,*.idb + +" Setup undo file +set undofile +let &undodir=stdpath('config') . '/undo' + +" Setup backup directory +let &backupdir=stdpath('config') . '/backup' + +" Enable mouse support +if has('mouse') + set mouse=a +endif + +" Functions +" ============================================================================== +" Increase font size using (Ctrl+Up Arrow) or (Ctrl+Down Arrow) if we are using +" gvim Otherwise font size is determined in terminal +nnoremap :silent! let &guifont = substitute( + \ &guifont, + \ ':h\zs\d\+', + \ '\=eval(submatch(0)+1)', + \ 'g') +nnoremap :silent! let &guifont = substitute( + \ &guifont, + \ ':h\zs\d\+', + \ '\=eval(submatch(0)-1)', + \ 'g') + +" Formatting options (see :h fo-table) +augroup persistent_settings + au! + au bufenter * :set formatoptions=q1j +augroup end + +" FZF +" ============================================================================== +" Empty value to disable preview window altogether +let g:fzf_preview_window = [] + +" Prefix all commands with Fzf for discoverability +let g:fzf_command_prefix = 'Fzf' + +" - down / up / left / right +let g:fzf_layout = { 'down': '40%' } + +" Add "FzfCustomRG" command which reinitializes +function! RipgrepFzf(query, fullscreen) + let command_fmt = 'rg --column --line-number --no-heading --color=always --smart-case -- %s || true' + let initial_command = printf(command_fmt, shellescape(a:query)) + let reload_command = printf(command_fmt, '{q}') + let spec = {'options': ['--phony', '--query', a:query, '--bind', 'change:reload:'.reload_command]} + call fzf#vim#grep(initial_command, 1, fzf#vim#with_preview(spec), a:fullscreen) +endfunction + +command! -nargs=* -bang FzfCustomRG call RipgrepFzf(, 0) + +" Augment the "FzfCustomFiles" command +command! -bang -nargs=? -complete=dir FzfCustomFiles + \ call fzf#vim#files(, {'options': ['--layout=reverse', '--info=inline', '--preview', 'cat {}']}, 0) + +" General Key Bindings +" ============================================================================== +" Telescope Bindings +nnoremap h FzfHistory +nnoremap f FzfCustomFiles +nnoremap g FzfCustomRG +nnoremap tt FzfTags +nnoremap tb FzfBTags +nnoremap cc FzfCommits +nnoremap cb FzfBCommits +nnoremap b FzfBuffers + +" Map Ctrl+HJKL to navigate buffer window +nmap :wincmd h +nmap :wincmd j +nmap :wincmd k +nmap :wincmd l + +" Move by wrapped lines instead of line numbers +nnoremap j gj +nnoremap k gk +nnoremap gj j +nnoremap gk k + +" Map NERDTree to Ctrl-N +map :NERDTreeToggle + +" Change to current buffer's directory +nmap cd :cd =expand("%:p:h") + +" Buffer Splitting +nnoremap s :vs + +" Go to next error +" Go to previous error +nnoremap :cn +nnoremap :cp + +" Clang Format +" ============================================================================== +map :py3file ~/clang-format.py + +" Compiler Error Formats +" ============================================================================== +" Error message formats thanks to +" https://forums.handmadehero.org/index.php/forum?view=topic&catid=4&id=704#3982 +set errorformat+=\\\ %#%f(%l\\\,%c):\ %m " MSVC: MSBuild +set errorformat+=\\\ %#%f(%l)\ :\ %#%t%[A-z]%#\ %m " MSVC: cl.exe +set errorformat+=\\\ %#%t%nxx:\ %m " MSVC: cl.exe, fatal errors is crudely implemented +set errorformat+=\\\ %#LINK\ :\ %m " MSVC: link.exe, can't find link library badly implemented +set errorformat+=\\\ %#%s\ :\ error\ %m " MSVC: link.exe, errors is badly implemented +set errorformat+=\\\ %#%s\ :\ fatal\ error\ %m " MSVC: link.exe, fatal errors is badly implemented +set errorformat+=\\\ %#%f(%l\\\,%c-%*[0-9]):\ %#%t%[A-z]%#\ %m " MSVC: HLSL fxc.exe +set errorformat+=%\\%%(CTIME%\\)%\\@=%m " ctime.exe -stats + +" Vim Dispatch +" ============================================================================== +let s:running_windows = has("win16") || has("win32") || has("win64") +if s:running_windows + set makeprg=build + nnoremap :Make ./build.bat +else + " Set vim terminal to enter normal mode using escape like normal vim behaviour + tnoremap + nnoremap :Make ./build.sh + set makeprg=./build.sh +endif diff --git a/devenver.py b/devenver.py new file mode 100644 index 0000000..3aad0f6 --- /dev/null +++ b/devenver.py @@ -0,0 +1,735 @@ +#!/usr/bin/env python3 + +# DEVenver +# ------------------------------------------------------------------------------ +# A simple python script to download portable applications and install them by +# unzipping them to a structured directory tree. + +import urllib.request +import urllib.parse +import pathlib +import os +import tempfile +import hashlib +import shutil +import subprocess +import pprint +import argparse +import json +import importlib + +from string import Template +from enum import Enum + +# Internal +# ------------------------------------------------------------------------------ +DOWNLOAD_CHUNK_SIZE = 1 * 1024 * 1024 # 1 megabyte +IS_WINDOWS = os.name == "nt" + +script_dir = os.path.dirname(os.path.abspath(__file__)) +default_base_dir = script_dir +default_base_downloads_dir = os.path.join(default_base_dir, 'Downloads') +default_base_install_dir = os.path.join(default_base_dir, 'Install') + +# Arguments +# ------------------------------------------------------------------------------ +arg_parser = argparse.ArgumentParser() +arg_parser.add_argument('--downloads-dir', + help=f'Set the directory where downloaded files are cached (default: {default_base_downloads_dir})', + default=default_base_downloads_dir, + type=pathlib.Path) + +arg_parser.add_argument('--install-dir', + help=f'Set the directory where downloaded files are installed (default: {default_base_install_dir})', + default=default_base_install_dir, + type=pathlib.Path) + +arg_parser.add_argument('--manifest-file', + help=f'Python file that has a get_manifest() function returning a dictionary of applications to download & install (see manifest.py for starters)', + required=True, + type=pathlib.Path) + +arg_parser.add_argument('--version', + action='version', + version='DEVenver v1') + + +args = arg_parser.parse_args() + +base_downloads_dir = args.downloads_dir +base_install_dir = args.install_dir + +# ------------------------------------------------------------------------------ +# This app list must always be installed, they provide the tools to install all +# other archives. Upon installation, we will collect the installation executable +# path and store them in global variables for the rest of the progam to use to +# unzip the files. +internal_app_list = [] + +internal_app_list.append({ + 'label': '7zip', + 'manifests': [], +}) + +version = "920" +internal_app_list[-1]['manifests'].append({ # Download the bootstrap 7zip, this can be unzipped using shutils + 'download_checksum': '2a3afe19c180f8373fa02ff00254d5394fec0349f5804e0ad2f6067854ff28ac', + 'download_url': f'https://www.7-zip.org/a/7za{version}.zip', + 'version': version, + 'executables': [ + { + 'path': '7za.exe', + 'symlink': [], + 'add_to_devenv_path': False, + 'checksum': 'c136b1467d669a725478a6110ebaaab3cb88a3d389dfa688e06173c066b76fcf' + } + ], + 'add_to_devenv_script': [], +}) + +version = "2201" +internal_app_list[-1]['manifests'].append({ # Download proper 7zip, extract this exe with the bootstrap 7zip + 'download_checksum': 'b055fee85472921575071464a97a79540e489c1c3a14b9bdfbdbab60e17f36e4', + 'download_url': f'https://www.7-zip.org/a/7z{version}-x64.exe', + 'version': version, + 'executables': [ + { + 'path': '7z.exe', + 'symlink': [], + 'add_to_devenv_path': True, + 'checksum': '254cf6411d38903b2440819f7e0a847f0cfee7f8096cfad9e90fea62f42b0c23' + } + ], + 'add_to_devenv_script': [], +}) + +# ------------------------------------------------------------------------------ + +version = "1.5.2" +internal_app_list.append({ + "label": "zstd", + "manifests": [ + { + "download_checksum": "68897cd037ee5e44c6d36b4dbbd04f1cc4202f9037415a3251951b953a257a09", + "download_url": f"https://github.com/facebook/zstd/releases/download/v{version}/zstd-v{version}-win64.zip", + "version": version, + "executables": [ + { + "path": "zstd.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "f14e78c0651851a670f508561d2c5d647da0ba08e6b73231f2e7539812bae311", + }, + ], + "add_to_devenv_script": [], + }, + ], +}) + +# ------------------------------------------------------------------------------ + +# These variables are set once they are downloaded dynamically and installed +# from the internal app listing! +zstd_exe = "" +zip7_exe = "" +zip7_bootstrap_exe = "" + + +# Functions +# ------------------------------------------------------------------------------ +def print_header(title): + line = f'> ' + title + ' '; + print(line.ljust(100, '-')) + +def lprint(*args, level=0, **kwargs): + print(' ' + (' ' * 2 * level), *args, **kwargs) + +def lexit(*args, level=0, **kwargs): + print(' ' + (' ' * 2 * level), *args, **kwargs) + exit() + +def verify_file_sha256(file_path, checksum, label): + if os.path.isfile(file_path) == False: + exit(f'Cannot verify SHA256, path is not a file [path={file_path}]') + + result = False + try: + file = open(file_path, 'r+b') + hasher = hashlib.sha256() + hasher.update(file.read()) + derived_checksum = hasher.hexdigest() + result = derived_checksum == checksum + if result: + lprint(f'- {label} SHA256 is good: {checksum}', level=1) + else: + lprint(f'- {label} SHA256 mismatch', level=1) + lprint(f' Expect: {checksum}', level=1) + lprint(f' Actual: {derived_checksum}', level=1) + except PermissionError as exception: + lprint(f"- {label} cannot verify SHA256 due to permission error, skipping", level=1) + result = True + else: + file.close() + + return result + +def download_file_at_url(url, download_path, download_checksum, label): + # Check if file already downloaded and hashes match + # -------------------------------------------------------------------------- + file_already_downloaded = False + if os.path.isfile(download_path): + lprint(f'- Cached archive found: {download_path}', level=1) + file_already_downloaded = verify_file_sha256(download_path, download_checksum, 'Cached archive') + else: + lprint(f'- Download to disk: {download_path}', level=1) + lprint(f' URL: {url}', level=1) + + # Download the file from URL + # -------------------------------------------------------------------------- + if file_already_downloaded == False: + lprint('Initiating download request ...', level=1) + with urllib.request.urlopen(url) as response: + temp_file = tempfile.mkstemp(text=False) + temp_file_handle = temp_file[0] + temp_file_path = temp_file[1] + temp_file_io = os.fdopen(temp_file_handle, mode='w+b') + + download_failed = False + try: + line = '' + total_download_size = int(response.getheader('Content-Length')) + bytes_downloaded = 0 + while chunk := response.read(DOWNLOAD_CHUNK_SIZE): + bytes_written = temp_file_io.write(chunk) + bytes_downloaded += bytes_written + percent_downloaded = int(bytes_downloaded / total_download_size * 100) + + lprint(' ' * len(line), end='\r', level=1) + line = f'Downloading {percent_downloaded:.2f}% ({bytes_downloaded}/{total_download_size})' + lprint(line, end='\r', level=1) + except Exception as exception: + download_failed = True + lprint(f'Download {label} from {url} failed, {exception}', level=1) + finally: + temp_file_io.close() + print() + + if download_failed == True: + os.remove(temp_file_path) + exit() + + os.rename(temp_file_path, download_path) + + if file_already_downloaded == False: + if verify_file_sha256(download_path, download_checksum, 'Downloaded archive') == False: + exit() + +class UnzipMethod(Enum): + SHUTILS = 0 + ZIP7_BOOTSTRAP = 1 + DEFAULT = 2 + +def get_exe_install_dir(install_dir, label, version_label): + result = pathlib.Path(install_dir, label.replace(' ', '_'), version_label) + return result + +def get_exe_install_path(install_dir, label, version_label, exe_rel_path): + install_dir = get_exe_install_dir(install_dir, label, version_label) + result = pathlib.Path(install_dir, exe_rel_path) + return result + +def get_exe_symlink_dir(install_dir): + result = pathlib.Path(install_dir, "Symlinks") + return result + +def download_and_install_archive(download_url, + download_checksum, + exe_list, + version_label, + label, + unzip_method, + download_dir, + install_dir): + + exe_install_dir = get_exe_install_dir(install_dir=install_dir, + label=label, + version_label=version_label) + + # Evaluate if we have already installed the requested archive + # -------------------------------------------------------------------------- + exes_are_not_a_file = [] + exes_missing = [] + exes_present = [] + for exe_dict in exe_list: + exe_path = get_exe_install_path(install_dir=install_dir, + label=label, + version_label=version_label, + exe_rel_path=exe_dict['path']) + + if os.path.exists(exe_path) == True: + if os.path.isfile(exe_path) == True: + exes_present.append(exe_dict) + else: + exes_are_not_a_file.append(exe_dict) + else: + exes_missing.append(exe_dict) + + # Executables not install yet, verify, download and install if possible + # -------------------------------------------------------------------------- + if len(exes_present) != len(exe_list): + + # Check if any of the manifest files are not files + # ---------------------------------------------------------------------- + if len(exes_are_not_a_file) > 0: # Some item exists at the path but they are not files + lprint(f'- {label} is installed but some of the expected executables are not a file!', level=1) + for exe_dict in exes_are_not_a_file: + lprint(f' {exe_dict["path"]}', level=1) + lprint(f' Installation cannot proceed as unpacking would overwrite these paths', level=1) + return + + # Check if any files are missing + # ---------------------------------------------------------------------- + # Some executables are missing, some are available, installation will + # trample over existing files, its not safe to unzip the archive as we may + # overwrite config files or some other files that have been modified by the + # program. + # + # Note that all files missing means we can assume that we haven't installed + # yet .. + if len(exes_missing) > 0 and len(exes_missing) != len(exe_list): + lprint(f'- {label} is installed but some of the expected executables are missing from the installation!', level=1) + for exe_dict in exes_are_not_a_file: + lprint(f' {exe_dict["path"]}', level=1) + lprint(f' Installation cannot proceed as unpacking could delete ', level=1) + return + + assert(len(exes_missing) == len(exe_list)) + assert(len(exes_present) == 0) + + # Not installed yet, download and install + # ---------------------------------------------------------------------- + # Determine the file name we are downloading from the URL + download_url_parts = urllib.parse.urlparse(download_url) + download_name = pathlib.Path(urllib.parse.unquote(download_url_parts.path)) + + # The path to move the temp file to after successful download, e.g. + # download_dir = C:/Dev/Downloads/Wezterm-windows-{version}.zip + # download_name = Wezterm-windows-{version}.zip + download_path = pathlib.Path(download_dir, download_name.name) + + # Download the archive at the URL + download_file_at_url(download_url, download_path, download_checksum, label) + + # Install the archive by unpacking it + # ---------------------------------------------------------------------- + if unzip_method == UnzipMethod.SHUTILS: + lprint(f'- SHUtils unzip install {label} to: {exe_install_dir}', level=1) + shutil.unpack_archive(download_path, exe_install_dir, 'zip') + else: + command = '' + if unzip_method == UnzipMethod.ZIP7_BOOTSTRAP: + command = f'"{zip7_bootstrap_exe}" x -bd "{download_path}" -o"{exe_install_dir}"' + lprint(f'- 7z (bootstrap) unzip {label} to: {exe_install_dir}', level=1) + lprint(f' Command: {command}', level=1) + subprocess.run(command) + else: + archive_path = download_path + intermediate_zip_file_extracted = False + + # We could have a "app.zst" situation or an "app.tar.zst" situation + # + # "app.zst" only needs 1 extraction from the zstd tool + # "app.tar.zst" needs 1 zstd extract and then 1 7zip extract + # + # When we have "app.tar.zst" we extract to the install folder, e.g. + # + # "app/1.0/app.tar" + # + # We call this an intermediate zip file, we will extract that file + # with 7zip. After we're done, we will delete that _intermediate_ + # file to cleanup our install directory. + if archive_path.suffix == '.zst': + + archive_without_suffix = pathlib.Path(str(archive_path)[:-len(archive_path.suffix)]).name + next_archive_path = pathlib.Path(exe_install_dir, archive_without_suffix) + + if os.path.exists(next_archive_path) == False: + command = f'"{zstd_exe}" --output-dir-flat "{exe_install_dir}" -d "{archive_path}"' + lprint(f'- zstd unzip {label} to: {exe_install_dir}', level=1) + lprint(f' Command: {command}', level=1) + + os.makedirs(exe_install_dir) + subprocess.run(command) + + # Remove the extension from the file, we just extracted it + archive_path = next_archive_path + + # If there's still a suffix after we removed the ".zst" we got + # an additional archive to unzip, e.g. "app.tar" remaining. + intermediate_zip_file_extracted = len(archive_path.suffix) > 0 + + if len(archive_path.suffix) > 0: + command = f'"{zip7_exe}" x -aoa -spe -bso0 "{archive_path}" -o"{exe_install_dir}"' + command = command.replace('\\', '/') + lprint(f'- 7z unzip install {label} to: {exe_install_dir}', level=1) + lprint(f' Command: {command}', level=1) + subprocess.run(command) + + if intermediate_zip_file_extracted: + lprint(f'- Detected intermediate zip file in install root, removing: {archive_path}', level=1) + os.remove(archive_path) + + # Remove duplicate root folder if detected + # ---------------------------------------------------------------------- + # If after unpacking, there's only 1 directory in the install direction, we + # assume that the zip contains a root folder. We will automatically merge + # the root folder to the parent. + has_files_in_install_dir = False + dir_count = 0 + dupe_root_folder_name = '' + with os.scandir(exe_install_dir) as scan_handle: + for it in list(scan_handle): + if it.is_file(): + has_files_in_install_dir = True + break + elif it.is_dir(): + dupe_root_folder_name = it.name + dir_count += 1 + if dir_count > 1: + break + + if dir_count == 1 and not has_files_in_install_dir: + # There is only one folder after we unzipped, what happened here is + # that the archive we unzipped had its contents within a root + # folder. We will pull those files out because we already unzipped + # into an isolated location for the application, e.g. + # + # Our install location C:/Dev/Install/7zip/920 + # After unzip C:/Dev/Install/7zip/920/7zip-920-x64 + # + # We have an duplicate '7zip-920-x64' directory in our + # installation. Move all the files in the duplicate directory up to + # our '920' folder then remove the duplicate folder. + dupe_root_folder_path = pathlib.Path(exe_install_dir, dupe_root_folder_name) + lprint(f'- Detected duplicate root folder after unzip: {dupe_root_folder_path}', level=1) + lprint(f' Merging duplicate root folder to parent: {exe_install_dir}', level=1) + for file_name in os.listdir(dupe_root_folder_path): + src = pathlib.Path(dupe_root_folder_path, file_name) + dest = pathlib.Path(exe_install_dir, file_name) + shutil.move(src, dest) + + os.rmdir(dupe_root_folder_path) + + # Verify the installation by checking the SHA256 of the executables + # -------------------------------------------------------------------------- + exes_with_bad_hashes = [] + for exe_dict in exe_list: + exe_rel_path = exe_dict['path'] + exe_path = get_exe_install_path(install_dir=install_dir, + label=label, + version_label=version_label, + exe_rel_path=exe_rel_path) + if os.path.isfile(exe_path) == False: + lexit(f'- Installed {label} but could not find expected file for validating install: {exe_path}', level=1) + + if verify_file_sha256(file_path=exe_path, checksum=exe_dict['checksum'], label=exe_rel_path) == False: + exes_with_bad_hashes.append(exe_dict) + + if len(exes_with_bad_hashes) > 0: + lprint(f'- {label} is installed but executable SHA256 does not match!', level=1) + lprint(f' See hashes above, executable path(s):', level=1) + for exe_dict in exes_with_bad_hashes: + lprint(f' {exe_dict["path"]}', level=1) + lprint(f' Something has modified the executable, this may be malicious or not!', level=1) + lprint(f' Manually uninstall the existing installation or amend the binary to be', level=1) + lprint(f' able to continue. Exiting.', level=1) + exit() + else: + lprint(f'- {label} installed and valid: {exe_install_dir}', level=1) + + # Do the symlinks + # -------------------------------------------------------------------------- + symlink_dir = get_exe_symlink_dir(install_dir) + paths_to_add_to_devenv_script = set() + for exe_dict in exe_list: + exe_rel_path = exe_dict['path'] + exe_path = get_exe_install_path(install_dir=install_dir, + label=label, + version_label=version_label, + exe_rel_path=exe_rel_path) + + for symlink_entry in exe_dict["symlink"]: + symlink_dest = symlink_dir / symlink_entry + symlink_src = exe_path + if os.path.exists(symlink_dest): + # Windows uses hardlinks because symlinks require you to enable "developer" mode + # Everyone else uses symlinks + if (IS_WINDOWS and not os.path.isfile(symlink_dest)) or (not IS_WINDOWS and not os.path.islink(symlink_dest)): + lprint( "- Cannot create symlink! The destionation file to create the symlink at.", level=1) + lprint( " already exists and is *not* a link. We cannot remove this safely as we", level=1) + lprint( " don't know what it is, exiting.", level=1) + lprint(f" Symlink Source: {symlink_src}", level=1) + lexit (f" Symlink Dest: {symlink_dest}", level=1) + + os.unlink(symlink_dest) + + if IS_WINDOWS == True: + os.link(src=symlink_src, dst=symlink_dest) + else: + os.symlink(src=symlink_src, dst=symlink_dest) + + # Collect paths to add to the devenv script + # ---------------------------------------------------------------------- + if exe_dict['add_to_devenv_path'] == True: + path = exe_path.parent.relative_to(install_dir) + paths_to_add_to_devenv_script.add(path) + + global devenv_script_buffer + for path in paths_to_add_to_devenv_script: + if IS_WINDOWS: + devenv_script_buffer += f"set PATH=\"%~dp0{path}\";%PATH%\n" + else: + devenv_script_buffer += f"PATH=\"$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd ){path}\";%PATH%\n" + +# Search the 2 dictionarries, 'first' and 'second' for the key. A matching key +# in 'first' taking precedence over the 'second' dictionary. If no key is +# found in either dictionaries then this function +# returns an empty string. +class ValidateAppListResult: + def __init__(self): + self.app_count = 0 + +def validate_app_list(app_list): + result = ValidateAppListResult() + manifest_rule_table = { + 'download_checksum': 'manifest must specify the SHA256 checksum for the downloaded file', + 'version': 'manifest must specify the app version that is to be installed', + 'executables': 'manifest must specify an array of executable(s) for verifying installation', + 'download_url': 'manifest must specify the URL to download the app from', + 'add_to_devenv_script': 'manifest must specify an array of strings to inject into the portable development environment setup script', + } + + executable_rule_table = { + 'path': 'executables must specify a path to a file from the installation to verify its checksum', + 'symlink': 'executables must specify an array of symlink names that will target the path', + 'add_to_devenv_path': 'executables must specify an boolean to indicate if the executable path should be added to the environment path', + 'checksum': 'executables must specify a string with the checksum of the executable', + } + + for app in app_list: + manifest_list = app['manifests'] + result.app_count += len(manifest_list) + + # Verify the label + # ---------------------------------------------------------------------- + label = app.get('label', '') + if 'label' not in app: + exit('Label missing from application list, app must have a label specified, e.g. { "label": "App Name", "manifests": [] }') + + # Verify that the mandatory keys are in the manifest + # ---------------------------------------------------------------------- + for manifest in manifest_list: + for key in manifest_rule_table: + value = manifest.get(key, "") + + if key.startswith("add_to_devenv"): + if not isinstance(value, list): + exit(f'{label} error: {key} in manifest must be an array to proceed\n{pprint.pformat(app)}') + + elif key == "executables": + for executable in value: + for executable_key in executable_rule_table: + executable_value = executable.get(executable_key, "") + + if executable_key == "path": + if not isinstance(executable_value, str) or len(executable_value) == 0: + exit(f'{label} error: required key "{executable_key}" is invalid, {executable_rule_table[executable_key]}\n{pprint.pformat(app)}') + + elif executable_key == "symlink": + if not isinstance(executable_value, list): + exit(f'{label} error: required key "{executable_key}" is invalid, {executable_rule_table[executable_key]}\n{pprint.pformat(app)}') + + elif executable_key == "add_to_devenv_path": + if not isinstance(executable_value, bool): + exit(f'{label} error: required key "{executable_key}" is invalid, {executable_rule_table[executable_key]}\n{pprint.pformat(app)}') + + elif executable_key == "checksum": + if not isinstance(executable_value, str): + exit(f'{label} error: required key "{executable_key}" is invalid, {executable_rule_table[executable_key]}\n{pprint.pformat(app)}') + + elif len(value) == 0: + exit(f'{label} error: required key "{key}" is missing/empty, {manifest_rule_table[key]}\n{pprint.pformat(app)}') + + return result + +devenv_script_buffer = """@echo off + +""" +def install_app_list(app_list, download_dir, install_dir): + title = "Internal Apps" if app_list is internal_app_list else "User Apps" + print_header(title) + result = {} + + validate_app_list_result = validate_app_list(app_list) + app_index = 0 + + for app in app_list: + manifest_list = app['manifests'] + for manifest in manifest_list: + app_index += 1 + + # Extract variables from manifest + # ------------------------------------------------------------------ + label = app["label"] + download_checksum = manifest['download_checksum'] + version = manifest["version"] + download_url = manifest["download_url"] + exe_list = manifest['executables'] + unzip_method = UnzipMethod.DEFAULT + + # Bootstrapping code, when installing the internal app list, we will + # assign the variables to point to our unarchiving tools. + # ------------------------------------------------------------------ + if app_list is internal_app_list: + global zip7_exe + global zip7_bootstrap_exe + global zstd_exe + exe_path = get_exe_install_path(install_dir, label, version, manifest['executables'][0]['path']) + if label == '7zip': + if version == '920': + unzip_method = UnzipMethod.SHUTILS + zip7_bootstrap_exe = exe_path + else: + unzip_method = UnzipMethod.ZIP7_BOOTSTRAP + zip7_exe = exe_path + + if label == 'zstd': + zstd_exe = exe_path + + + # Download and install + # ------------------------------------------------------------------ + lprint(f'[{app_index:03}/{validate_app_list_result.app_count:03}] Setup {label} v{version}', level=0) + download_and_install_archive(download_url=download_url, + download_checksum=download_checksum, + exe_list=exe_list, + version_label=version, + label=label, + unzip_method=unzip_method, + download_dir=download_dir, + install_dir=install_dir) + + # Post-installation + # ------------------------------------------------------------------ + + # Collate results into results + if label not in result: + result.update({label: []}) + + for item in exe_list: + app_install_dir = get_exe_install_dir(install_dir, label, version) + app_exe_path = get_exe_install_path(install_dir, label, version, item['path']) + app_exe_dir = pathlib.Path(app_exe_path).parent + + # Add executable into the result list + result[label].append({ + 'version': version, + 'install_dir': app_install_dir, + 'exe_path': app_exe_path, + }) + + # Add the snippets verbatim specified in the manifest + global devenv_script_buffer + for line in manifest['add_to_devenv_script']: + devenv_script_buffer += (line + '\n') + + if app_list is internal_app_list: + if len(str(zip7_exe)) == 0 or len(str(zip7_bootstrap_exe)) == 0 or len(str(zstd_exe)) == 0: + exit("Internal app list did not install 7zip bootstrap, 7zip or zstd, we are unable to install archives\n" + f" - zip7_bootstrap_exe: {zip7_bootstrap_exe}\n" + f" - zip7_exe: {zip7_exe}\n" + f" - zstd_exe: {zstd_exe}\n") + + return result + +def run(user_app_list, + download_dir=base_downloads_dir, + install_dir=base_install_dir): + """ Download and install the given user app list at the specified + directories. The apps given must be archives that can be unpacked for + installation (e.g. portable distributions). + + Parameters: + user_app_list (list): A list of dictionaries that contain app and + manifest information dictating what is to be installed via this + function. + download_dir (string): The path that intermediate downloaded files will + be kept at. + install_dir (string): The path that installed applications will be + unpacked to + + Returns: + result (list): A list of dictionaries containing the install locations + of each app, e.g. + """ + # Run + # -------------------------------------------------------------------------- + # Create the starting directories and install the internal app list (e.g. + # 7zip) which will be used to unzip-install the rest of the apps in the user + # app list. + # + # To do this without dependencies, we first download an old version of 7zip, + # version 9.20 which is distributed as a .zip file which Python can natively + # unzip. + # + # We then use the old version of 7zip and download a newer version of 7zip + # and extract it using the bootstrap-ed version. As of writing this, 7zip + # does not release a portable distribution of itself yet, instead what we do + # is download the installer and extract it ourselves using the bootstrap + # 7zip. + + # Create the paths requested by the user + os.makedirs(download_dir, exist_ok=True) + os.makedirs(install_dir, exist_ok=True) + os.makedirs(pathlib.Path(install_dir, "Symlinks"), exist_ok=True) + + for path in [download_dir, install_dir]: + if not os.path.isdir(path): + exit(f'Path "{path}" is not a directory, script can not proceed. Exiting.') + + # Validate all the manifests before starting + internal_app_validate_result = validate_app_list(internal_app_list) + user_app_validate_result = validate_app_list(user_app_list) + + # Install apps + internal_apps = install_app_list(app_list=internal_app_list, + download_dir=download_dir, + install_dir=install_dir) + + user_apps = install_app_list(app_list=user_app_list, + download_dir=download_dir, + install_dir=install_dir) + + # Write the devenv script with environment variables + global devenv_script_buffer + devenv_script_buffer += "set PATH=\"%~dp0Symlinks\";%PATH%\n" + + devenv_script_name = "devenv.bat" if IS_WINDOWS else "devenv.sh" + devenv_script_path = pathlib.Path(install_dir, devenv_script_name) + lprint(f"Writing script to augment the environment with installed applications: {devenv_script_path}") + with open(devenv_script_path, 'w') as file: + file.write(devenv_script_buffer) + + # Merge the install dictionaries, this dictionary contains + # (app label) -> [array of installed versions] + result = internal_apps + for key, value in user_apps.items(): + if key not in result: + result.update({key: value}) + else: + result[key] += value + + return result + +if __name__ == '__main__': + run() diff --git a/devenver_manifest.py b/devenver_manifest.py new file mode 100644 index 0000000..bc430cd --- /dev/null +++ b/devenver_manifest.py @@ -0,0 +1,976 @@ +def get_manifest(): + result = [] + + # -------------------------------------------------------------------------- + + version = "20221119-145034-49b9839f" + result.append({ + "label": "WezTerm", + "manifests": [ + { + "version": version, + "download_checksum": "7041d2c02d226c0c051cc9f6373d51ac9a2de00025e18582077c76e8ad68abe1", + "download_url": f"https://github.com/wez/wezterm/releases/download/{version}/WezTerm-windows-{version}.zip", + "executables": [ + { + "path": "wezterm-gui.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "e3faa247d69a8a966302a2ab4e655b08b79548707db79a7b724cf18cccf5ae35", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "2.304" + result.append({ + "label": "JetBrains_Mono_Font", + "manifests": [ + { + "download_checksum": "6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf", + "version": "2.304", + "download_url": f"https://download.jetbrains.com/fonts/JetBrainsMono-{version}.zip", + "executables": [ + { + "path": "fonts/ttf/JetBrainsMono-Regular.ttf", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "a0bf60ef0f83c5ed4d7a75d45838548b1f6873372dfac88f71804491898d138f", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + result.append({ + "label": "CMake", + "manifests": [], + }) + + version = "3.23.4" + result[-1]['manifests'].append({ + "download_checksum": "df15113aaab9e5f8cac254e02cf23f70d02407c9bf2983c82a9fe0d35bd20682", + "download_url": f"https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-windows-x86_64.zip", + "version": "3.23.4", + "executables": [ + { + "path": "bin/cmake.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "426074cd812586551fbab2bde67377113e2085c78c2e9a887748e85b4dc3dda5", + } + ], + "add_to_devenv_script": [], + }) + + version = "3.10.3" + result[-1]['manifests'].append({ + "download_checksum": "3bd57d1cfcf720a4cc72db77bda4c76a7b700fb0341821ad868963ad28856cd0", + "download_url": f"https://github.com/Kitware/CMake/releases/download/v{version}/cmake-{version}-win64-x64.zip", + "version": version, + "executables": [ + { + "path": "bin/cmake.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "f2e3b486d87d2a6bc19b3a62c740028f3f8945875196ac7d3d0e69649e98730a", + } + ], + "add_to_devenv_script": [], + }) + + # -------------------------------------------------------------------------- + + version = "1.9.4" + result.append({ + "label": "Doxygen", + "manifests": [ + { + "download_checksum": "3b34098c5fb016baa1d29aba101fe9d6843213b966b92a6b12c8856c547ee0c4", + "download_url": f"https://github.com/doxygen/doxygen/releases/download/Release_{version.replace('.', '_')}/doxygen-{version}.windows.x64.bin.zip", + "version": version, + "executables": [ + { + "path": "doxygen.exe", + "symlink": [f"doxygen-{version}.exe"], + "add_to_devenv_path": True, + "checksum": "3cb4d89f2b3db7eec2b6797dc6b49cdfe9adda954575898895260f66f312d730", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "2.39.1" + label = "Git" + result.append({ + "label": f"{label}", + "manifests": [ + { + "download_checksum": "b898306a44084b5fa13b9a52e06408d97234389d07ae41d9409bdf58cad3d227", + "download_url": f"https://github.com/git-for-windows/git/releases/download/v{version}.windows.1/PortableGit-{version}-64-bit.7z.exe", + "version": version, + "executables": [ + { + "path": "cmd/git.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "2fc6d5be237efb6b429d8f40975f1a1cfe3bcac863d9335e24096c8b0ec38105", + } + ], + "add_to_devenv_script": [ + f"set PATH=\"%~dp0{label}\\{version}\\mingw64\\bin\";%PATH%", + f"set PATH=\"%~dp0{label}\\{version}\\usr\\bin\";%PATH%", + ], + } + ], + }) + + # -------------------------------------------------------------------------- + + result.append({ + "label": "GCC_MinGW_AArch64", + "manifests": [], + }) + + arch = "aarch64-none-elf" + version = "12.2.0" + + result[-1]['manifests'].append({ + "download_checksum": "729e8af6aecd85cce63435b94c310c01983091b5db54842cd6604298f29d047f", + "download_url": f"https://github.com/mmozeiko/build-gcc-arm/releases/download/gcc-v{version}/gcc-v{version}-{arch}.7z", + "version": version, + "executables": [ + { + "path": f"bin/{arch}-g++.exe", + "checksum": "a26baffa86bc3401790d682f13f9b321ea56153eae7dd4f332bde40a6b76fcb3", + "symlink": [f"{arch}-g++-{version}.exe"], + "add_to_devenv_path": True, + }, + { + "path": f"bin/{arch}-gcc.exe", + "checksum": "0f594c7e741207f1613aa958369b12fab781741718688710b7082cac172fadf5", + "symlink": [f"{arch}-gcc-{version}.exe"], + "add_to_devenv_path": True, + }, + ], + "add_to_devenv_script": [], + }) + + version = "11.3.0" + result[-1]['manifests'].append({ + "download_checksum": "a000bdeeb225145a1450c1b9b1094ef71c13fc4de2ab300a65acbf51cd107c7d", + "download_url": f"https://github.com/mmozeiko/build-gcc-arm/releases/download/gcc-v{version}/gcc-v{version}-{arch}.7z", + "version": version, + "executables": [ + { + "path": f"bin/{arch}-g++.exe", + "checksum": "47eaef0e603c9fcae18f2efada305888503e878053119ede3a9e0b8b8beac2ee", + "symlink": [f"{arch}-g++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/{arch}-gcc.exe", + "checksum": "205d0b05d64bc80908deb5a64e5f3bf8769cfc08b272835f97aaeaec13ccd533", + "symlink": [f"{arch}-gcc-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + version = "10.3.0" + result[-1]['manifests'].append({ + "download_checksum": "095ab5a12059fa5dc59f415c059eb577f443a766eb1dd312fbede0f59940f432", + "download_url": f"https://github.com/mmozeiko/build-gcc-arm/releases/download/gcc-v{version}/gcc-v{version}-{arch}.7z", + "version": version, + "executables": [ + { + "path": f"bin/{arch}-g++.exe", + "checksum": "f2b2d3c6dab0f84a151835540f25e6d6f9442d00bf546bc4c709fad4b6fdda06", + "symlink": [f"{arch}-g++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/{arch}-gcc.exe", + "checksum": "95a8478ecb5133029f3058fb0207f19ee00157a6dd81f220e8308305f0e25fe8", + "symlink": [f"{arch}-gcc-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + # -------------------------------------------------------------------------- + + result.append({ + "label": "GCC_MinGW_ARM", + "manifests": [], + }) + + arch = "arm-none-eabi" + version = "12.2.0" + result[-1]['manifests'].append({ + "download_checksum": "aa581b3a5d446bb2d9827f2ea1f02b066b6713d4543d24abbd3181f626036c39", + "download_url": f"https://github.com/mmozeiko/build-gcc-arm/releases/download/gcc-v{version}/gcc-v{version}-{arch}.7z", + "version": version, + "executables": [ + { + "path": f"bin/{arch}-g++.exe", + "checksum": "fa48985c43cf82b426c461381e4c50d0ac3e9425f7e97bf116e1bab4b3a2a388", + "symlink": [f"{arch}-g++-{version}.exe"], + "add_to_devenv_path": True, + }, + { + "path": f"bin/{arch}-gcc.exe", + "checksum": "94a342aae99cae1a95f3636bb5c7b11f5e17015aee98b556989944ec38755be2", + "symlink": [f"{arch}-gcc-{version}.exe"], + "add_to_devenv_path": True, + }, + ], + "add_to_devenv_script": [], + }) + + version = "11.3.0" + result[-1]['manifests'].append({ + "download_checksum": "797ed71f60fae386c8875bb4e75e244afb15ded9e00ac77b6670a62be7614cc6", + "download_url": f"https://github.com/mmozeiko/build-gcc-arm/releases/download/gcc-v{version}/gcc-v{version}-{arch}.7z", + "version": version, + "executables": [ + { + "path": f"bin/{arch}-g++.exe", + "checksum": "a36f2ea6846badf7c91631f118e88967f25d6e479a9beea158445ce75403a655", + "symlink": [f"{arch}-g++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/{arch}-gcc.exe", + "checksum": "71158642a3d4921eda106a1b23640f1ed8bf1725ceaa98cbc8729a9a8115b09a", + "symlink": [f"{arch}-gcc-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + version = "10.3.0" + result[-1]['manifests'].append({ + "download_checksum": "af0fc2da062aa6423a91213e231ecc5981136b9b0655837ebdbbc5ad879d2d9e", + "download_url": f"https://github.com/mmozeiko/build-gcc-arm/releases/download/gcc-v{version}/gcc-v{version}-{arch}.7z", + "version": version, + "executables": [ + { + "path": f"bin/{arch}-g++.exe", + "checksum": "c3dc49b561d177b3586992dfea86067eb8799e1586a7f26cea5b0ea97926632e", + "symlink": [f"{arch}-g++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/{arch}-gcc.exe", + "checksum": "7e680ffec593474a54193f5253b620cf59b6e3a1720dd35ab95bcb53582b7b7d", + "symlink": [f"{arch}-gcc-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + # -------------------------------------------------------------------------- + + result.append({ + "label": "GCC_MinGW", + "manifests": [], + }) + + version = "12.2.0" + mingw_version = "10.0.0" + result[-1]['manifests'].append({ + "download_checksum": "5cbe5ea7533f6d24af3a57fe7022032f420b15d7c4e38c0d16534a42d33213a4", + "download_url": f"https://github.com/mmozeiko/build-gcc/releases/download/gcc-v{version}-mingw-v{mingw_version}/gcc-v{version}-mingw-v{mingw_version}-x86_64.7z", + "version": version, + "executables": [ + { + "path": f"bin/g++.exe", + "checksum": "886b0f25256ddbd0f4ad09e6e3b81279f9a8b6a1b5c32c714c9c201d802caa39", + "symlink": [f"g++-{version}.exe"], + "add_to_devenv_path": True, + }, + { + "path": f"bin/gcc.exe", + "checksum": "91c910fa5257fdfd0291c347c81a73c7facb1f486dba941f977714672895c96e", + "symlink": [f"gcc-{version}.exe"], + "add_to_devenv_path": True, + }, + ], + "add_to_devenv_script": [], + }) + + version = "11.3.0" + result[-1]['manifests'].append({ + "download_checksum": "e2c5c64659aeda77680c5eec80bbaa4db3f117b21febeb3f13fd76d580604fd0", + "download_url": f"https://github.com/mmozeiko/build-gcc/releases/download/gcc-v{version}-mingw-v{mingw_version}/gcc-v{version}-mingw-v{mingw_version}-x86_64.7z", + "version": version, + "executables": [ + { + "path": f"bin/g++.exe", + "checksum": "e92ecfa0171f2ab0c3ca39f2121ab5e887b3a378399a4be7e056820f5841c7a5", + "symlink": [f"g++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/gcc.exe", + "checksum": "f3226120196ea37ab3e450bd0f26d816ee28556d18aa0de64c3e427f31d66eeb", + "symlink": [f"gcc-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + version = "10.3.0" + mingw_version = "8.0.0" + result[-1]['manifests'].append({ + "download_checksum": "c8f38f6b1d264d7e008009bd32a04ca71b4ee3a3113e67930ab31c2e06818317", + "download_url": f"https://github.com/mmozeiko/build-gcc/releases/download/gcc-v{version}-mingw-v{mingw_version}/gcc-v{version}-mingw-v{mingw_version}-x86_64.7z", + "version": version, + "executables": [ + { + "path": f"bin/g++.exe", + "checksum": "5c93b6da129ea01ee5fc87d5c7db948fc3bc62bae261ded9a883f1fa543571d2", + "symlink": [f"g++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/gcc.exe", + "checksum": "54a5f8d09e6741b9c94d1494f383c424c20449e3e06f36bf96603aeda9874405", + "symlink": [f"gcc-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + # -------------------------------------------------------------------------- + + result.append({ + "label": "LLVM", + "manifests": [], + }) + + version = "15.0.7" + result[-1]['manifests'].append({ + "download_checksum": "5428cb72acf63ce3bc4328e546a36674c9736ec040ecc176d362201c6548e6a8", + "download_url": f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/LLVM-{version}-win64.exe", + "version": version, + "executables": [ + { + "path": f"bin/clang++.exe", + "checksum": "1f523e33de4ce9d591b4eb9bad102f086e8480488148f8db0d5c87056798ce3e", + "symlink": [f"clang++-{version}.exe"], + "add_to_devenv_path": True, + }, + { + "path": f"bin/clang.exe", + "checksum": "1f523e33de4ce9d591b4eb9bad102f086e8480488148f8db0d5c87056798ce3e", + "symlink": [f"clang-{version}.exe"], + "add_to_devenv_path": True, + }, + ], + "add_to_devenv_script": [], + }) + + version = "14.0.6" + result[-1]['manifests'].append({ + "download_checksum": "e8dbb2f7de8e37915273d65c1c2f2d96844b96bb8e8035f62c5182475e80b9fc", + "download_url": f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/LLVM-{version}-win64.exe", + "version": version, + "executables": [ + { + "path": f"bin/clang++.exe", + "checksum": "d557b79bc09a01141ac7d940016f52ce1db081e31d7968f0d9b6f4c192d8f8cc", + "symlink": [f"clang++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/clang.exe", + "checksum": "d557b79bc09a01141ac7d940016f52ce1db081e31d7968f0d9b6f4c192d8f8cc", + "symlink": [f"clang-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + version = "13.0.1" + result[-1]['manifests'].append({ + "download_checksum": "9d15be034d52ec57cfc97615634099604d88a54761649498daa7405983a7e12f", + "download_url": f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/LLVM-{version}-win64.exe", + "version": version, + "executables": [ + { + "path": f"bin/clang++.exe", + "checksum": "e3f26820ac446cb7c471cce49f6646b4346aa5380d11790ceaa7bf494a94b21d", + "symlink": [f"clang++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/clang.exe", + "checksum": "e3f26820ac446cb7c471cce49f6646b4346aa5380d11790ceaa7bf494a94b21d", + "symlink": [f"clang-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + version = "12.0.1" + result[-1]['manifests'].append({ + "download_checksum": "fcbabc9a170208bb344f7bba8366cca57ff103d72a316781bbb77d634b9e9433", + "download_url": f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/LLVM-{version}-win64.exe", + "version": version, + "executables": [ + { + "path": f"bin/clang++.exe", + "checksum": "9f0748de7f946c210a030452de226986bab46a0121d7236ea0e7b5079cb6dfef", + "symlink": [f"clang++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/clang.exe", + "checksum": "9f0748de7f946c210a030452de226986bab46a0121d7236ea0e7b5079cb6dfef", + "symlink": [f"clang-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + version = "11.1.0" + result[-1]['manifests'].append({ + "download_checksum": "b5770bbfac712d273938cd155e232afaa85c2e8d865c7ca504a104a838568516", + "download_url": f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/LLVM-{version}-win64.exe", + "version": version, + "executables": [ + { + "path": f"bin/clang++.exe", + "checksum": "f72591f8a02e4b7573aa2fcd2999a3ea76fe729e2468e5414853617268798dfd", + "symlink": [f"clang++-{version}.exe"], + "add_to_devenv_path": False, + }, + { + "path": f"bin/clang.exe", + "checksum": "f72591f8a02e4b7573aa2fcd2999a3ea76fe729e2468e5414853617268798dfd", + "symlink": [f"clang-{version}.exe"], + "add_to_devenv_path": False, + }, + ], + "add_to_devenv_script": [], + }) + + # -------------------------------------------------------------------------- + + version = "1.11.1" + result.append({ + "label": "Ninja", + "manifests": [ + { + "download_url": f"https://github.com/ninja-build/ninja/releases/download/v{version}/ninja-win.zip", + "download_checksum": "524b344a1a9a55005eaf868d991e090ab8ce07fa109f1820d40e74642e289abc", + "version": version, + "executables": [ + { + "path": "ninja.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "23e7d60c17b3fcd42d9c00d49eca3c3771b04d7ccb13e49836b06b34e20211c7", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "16.19.0" + result.append({ + "label": "NodeJS", + "manifests": [ + { + "download_checksum": "e07399a4a441091ca0a5506faf7a9236ea1675220146daeea3bee828c2cbda3f", + "download_url": f"https://nodejs.org/dist/v{version}/node-v{version}-win-x64.7z", + "version": version, + "executables": [ + { + "path": "node.exe", + "symlink": [f"node-{version}.exe"], + "add_to_devenv_path": True, + "checksum": "e4e7f389fbec9300275defc749246c62bdbe4f66406eb01e7c9a4101e07352da", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + date = "20230116" + version = f"3.10.9+{date}" + label = "Python" + result.append({ + "label": f"{label}", + "manifests": [ + { + "download_checksum": "4cfa6299a78a3959102c461d126e4869616f0a49c60b44220c000fc9aecddd78", + "download_url": f"https://github.com/indygreg/python-build-standalone/releases/download/{date}/cpython-{version}-x86_64-pc-windows-msvc-shared-pgo-full.tar.zst", + "version": version, + "executables": [ + { + "path": "install/python.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "6dafb845aba67aba898f5aa8adf6c48061e7ffea1d2ed7d290a1e4386e78f2f0", + } + ], + "add_to_devenv_script": [ + f"set PYTHONHOME=%~dp0{label}\\{version}\\install", + f"set PATH=\"%~dp0{label}\\{version}\\install\\Script\";%PATH%", + ], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "1.24" + result.append({ + "label": "Renderdoc", + "manifests": [ + { + "download_url": f"https://renderdoc.org/stable/{version}/RenderDoc_{version}_64.zip", + "download_checksum": "dbd215f7e1c7933b8eedc49499a4372c92e68ddab04af4658f434bfe6c382a9a", + "version": version, + "executables": [ + { + "path": "qrenderdoc.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "cfb96468355a416568faf89db18cd8a195bccec87ea16b3fffd3cc13c952c5fd", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "0.6.1" + result.append({ + "label": "Zeal", + "manifests": [ + { + "download_url": f"https://github.com/zealdocs/zeal/releases/download/v{version}/zeal-portable-{version}-windows-x64.7z", + "download_checksum": "08e9992f620ba0a5ea348471d8ac9c85059e95eedd950118928be639746e3f94", + "version": version, + "executables": [ + { + "path": "zeal.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "d1e687a33e117b6319210f40e2401b4a68ffeb0f33ef82f5fb6a31ce4514a423", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "0.10.1" + result.append({ + "label": "Zig", + "manifests": [ + { + "download_url": f"https://ziglang.org/download/{version}/zig-windows-x86_64-{version}.zip", + "download_checksum": "5768004e5e274c7969c3892e891596e51c5df2b422d798865471e05049988125", + "version": version, + "executables": [ + { + "path": "zig.exe", + "symlink": [f"zig-{version}.exe"], + "add_to_devenv_path": True, + "checksum": "607c9928a24f9d2e08df1ee240ebfd15ab1eb3c14b85e02f7dad6f8c8b53fea8", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "1.4.13" + git_hash = "0066c6" + result.append({ + "label": "clink", + "manifests": [ + { + "download_url": f"https://github.com/chrisant996/clink/releases/download/v{version}/clink.{version}.{git_hash}.zip", + "download_checksum": "800f7657d73a00dad40d46c9317bd418172ee40cc8b3958e32fba1f0b596e829", + "version": version, + "executables": [ + { + "path": "clink_x64.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "331266334f59f2c978ff8e13bbcadb218051e790b61d9cc69e85617276c51298", + } + ], + "add_to_devenv_script": [ + "set CLINK_PATH=%~dp0clink-completions" + ], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "1.11.1" + result.append({ + "label": "Dependencies", + "manifests": [ + { + "download_url": f"https://github.com/lucasg/Dependencies/releases/download/v{version}/Dependencies_x64_Release.zip", + "download_checksum": "7d22dc00f1c09fd4415d48ad74d1cf801893e83b9a39944b0fce6dea7ceaea99", + "version": version, + "executables": [ + { + "path": "DependenciesGui.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "1737e5406128c3560bbb2bced3ac62d77998e592444f94b10cc0aa0bb1e617e6", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "1.4.1.1022" + result.append({ + "label": "Everything", + "manifests": [ + { + "download_url": f"https://www.voidtools.com/Everything-{version}.x64.zip", + "download_checksum": "c718bcd73d341e64c8cb47e97eb0c45d010fdcc45c2488d4a3a3c51acc775889", + "version": version, + "executables": [ + { + "path": "Everything.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "9c282a47a18477af505e64b45c3609f21f13fe1f6ff289065497a1ec00f5d332", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "0.37.0" + result.append({ + "label": "fzf", + "manifests": [ + { + "download_url": f"https://github.com/junegunn/fzf/releases/download/{version}/fzf-{version}-windows_amd64.zip", + "download_checksum": "247bffe84ff3294a8c0a7bb96329d5e4152d3d034e13dec59dcc97d8a828000d", + "version": version, + "executables": [ + { + "path": "fzf.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "c0f4b20d0602977ff3e592cac8eadf86473abed0d24e2def81239bd2e76047e8", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "1.1.41.1" + result.append({ + "label": "jpegview", + "manifests": [ + { + "download_url": f"https://github.com/sylikc/jpegview/releases/download/v{version}/JPEGView_{version}.7z", + "download_checksum": "7dd4b4b34b14e5fae331c7f4ebfb658be6684c70ec055cb1964642a8b45e4886", + "version": version, + "executables": [ + { + "path": "JPEGView64/JPEGView.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "f4fe2308c932a5f4f41f67b0520fe1fe8a96c94169d98c83f9501e9dc84b56ad", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "22.02" + result.append({ + "label": "mpc-qt", + "manifests": [ + { + "download_url": f"https://github.com/sylikc/jpegview/releases/download/v{version}/JPEGView_{version}.7z", + "download_checksum": "2230c4f4de1a429ccc67e5c590efc0a86fbaffeb33a4dc5f391aa45e660b80c2", + "version": version, + "executables": [ + { + "path": "mpc-qt.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "d7ee46b0d4a61a26f8acd5d5fd4da2d252d6bc80c5cab6a55db06e853f2acefb", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "0.8.2" + result.append({ + "label": "nvim", + "manifests": [ + { + "download_url": f"https://github.com/neovim/neovim/releases/download/v{version}/nvim-win64.zip", + "download_checksum": "e2d53c6fd4a3caefbff47765d63d1640a5a134de46623ed8e3f9bf547791c26f", + "version": version, + "executables": [ + { + "path": "bin/nvim.exe", + "symlink": [], + "add_to_devenv_path": True, + "checksum": "dd8b045e9a76bea6add3e7a727387aef6996846907e061df07971329b9464faf", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "0.10.3" + result.append({ + "label": "neovide", + "manifests": [ + { + "download_url": f"https://github.com/neovide/neovide/releases/download/{version}/neovide-windows.zip", + "download_checksum": "ec54f811e5cb271102751694124380f4a58ae5edf99a1a267e8b070a362f8297", + "version": version, + "executables": [ + { + "path": "neovide.exe", + "symlink": ["neovide.exe"], + "add_to_devenv_path": False, + "checksum": "2c1df8ec7287f927554ebd9ad5cd0da34d7e72c3384fe266080ddf612adf6e5a", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "1.26.2" + result.append({ + "label": "ImHex", + "manifests": [ + { + "download_url": f"https://github.com/WerWolv/ImHex/releases/download/v{version}/imhex-{version}-Windows-Portable.zip", + "download_checksum": "4f58097c3ccee88d8dff0d48da0f239af8a9d444903cc19a3369f63caa8d77e6", + "version": f"version", + "executables": [ + { + "path": "imhex.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "ddd448c0d8fe71295bbcc5b52c9e9f4b06956a79572b7d634436a49728f5f341", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "22.3" + result.append({ + "label": "MobaXTerm", + "manifests": [ + { + "download_url": f"https://download.mobatek.net/2232022120824733/MobaXterm_Portable_v{version}.zip", + "download_checksum": "c8de508d6731f31a73f061e58942691466d1d24cfa941e642e16e0930be2fad9", + "version": version, + "executables": [ + { + "path": f"MobaXTerm_Personal_{version}.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "e47cb54645a368411c5d6b6cbfa7e25980a2a674d7d0c082f5137b6e77a2f362", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "3.0.5847" + result.append({ + "label": "SystemInformer", + "manifests": [ + { + "download_url": f"https://github.com/winsiderss/si-builds/releases/download/{version}/systeminformer-{version}-bin.zip", + "download_checksum": "4557e58f698048e882515faac89c9c7f654247dbf4bd656ceed5c3f97afef77d", + "version": "3.0.5847", + "executables": [ + { + "path": "amd64/SystemInformer.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "8a6e9dfd145e5cb8d03ec3db1b7b0163325be33e5c8fdd4126e9f8df2af2a39c", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "13.0.0" + result.append({ + "label": "ripgrep", + "manifests": [ + { + "download_url": f"https://github.com/BurntSushi/ripgrep/releases/download/{version}/ripgrep-{version}-x86_64-pc-windows-msvc.zip", + "download_checksum": "a47ace6f654c5ffa236792fc3ee3fefd9c7e88e026928b44da801acb72124aa8", + "version": version, + "executables": [ + { + "path": "rg.exe", + "symlink": ["rg.exe"], + "add_to_devenv_path": False, + "checksum": "ab5595a4f7a6b918cece0e7e22ebc883ead6163948571419a1dd5cd3c7f37972", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "2.0.0" + result.append({ + "label": "sioyek", + "manifests": [ + { + "download_url": f"https://github.com/ahrm/sioyek/releases/download/v{version}/sioyek-release-windows-portable.zip", + "download_checksum": "1f4fedbb38c0dc46bbba4bb95d0d6fab39fcf3525092ac26d92c891684d2bf8d", + "version": version, + "executables": [ + { + "path": "sioyek.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "6c660f0f7265fabe6d943d15d9b5c7e85f2dbcf7fecb7d2cd0639e7086b1c034", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "8.6.0" + result.append({ + "label": "fd", + "manifests": [ + { + "download_url": f"https://github.com/sharkdp/fd/releases/download/v{version}/fd-v{version}-x86_64-pc-windows-msvc.zip", + "download_checksum": "9cff97eb1c024ed94cc76a4b2d924ab3df04b37e7430c282b8188a13f1653ebe", + "version": version, + "executables": [ + { + "path": "fd.exe", + "symlink": ["fd.exe"], + "add_to_devenv_path": False, + "checksum": "a93ab08528896556ba3a6c262c8d73b275df2ce7a4138f5323f3eff414403f33", + } + ], + "add_to_devenv_script": [ + "set FZF_DEFAULT_OPTS=--multi --layout=reverse", + "set FZF_DEFAULT_COMMAND=fd --type f --strip-cwd-prefix --hidden --follow --exclude .git --exclude .cache --exclude .vs", + ], + } + ], + }) + + # -------------------------------------------------------------------------- + + version = "4_12" + result.append({ + "label": "WizTree", + "manifests": [ + { + "download_url": f"https://www.diskanalyzer.com/files/wiztree_{version}_portable.zip", + "download_checksum": "f6b71fc54a9bb3f277efdf8afcd45df8ddc1759533f3236437309dae7778b168", + "version": version, + "executables": [ + { + "path": "wiztree64.exe", + "symlink": [], + "add_to_devenv_path": False, + "checksum": "e2157dc64629a29e1713a845e5a9e7cab89d79a7390820c1bfda05c7de989c3d", + } + ], + "add_to_devenv_script": [], + } + ], + }) + + # -------------------------------------------------------------------------- + + return result diff --git a/win_portable_msvc.py b/win_portable_msvc.py new file mode 100644 index 0000000..963911b --- /dev/null +++ b/win_portable_msvc.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 + +# This script has been gratefully sourced from Martins Mozeiko of HMN +# https://gist.github.com/mmozeiko/7f3162ec2988e81e56d5c4e22cde9977 +# +# Further modifications by https://github.com/doy-lee with the primary purpose +# of facilitating multiple versions to be stored in the same root directory +# ('Redist' in the SDK was unversioned, store it versioned like all other +# folders, skip the downloading of MSVC or the SDK if we only need one of them). +# +# Changelog +# 2023-01-28 +# - Inital revision from mmozeiko +# https://gist.github.com/mmozeiko/7f3162ec2988e81e56d5c4e22cde9977/6863f19cb98b933c7535acf3d59ac64268c6bd1b +# - Add individual scripts to source variables for MSVC and Windows 10 +# separately "msvc-{version}.bat" and "win-sdk-{version}.bat" +# - Add '--no-sdk' and '--no-msvc' to prevent the download and installation of +# the Windows SDK and MSVC respectively. +# - Installation used to create 'Windows Kit/10/Redist' and unpack a D3D and MBN +# folder without being versioned. These folders are now placed under +# a versioned sub-directory to preserve the binaries and allow subsequent +# side-by-side installation of other versions of the SDK. + +import io +import os +import sys +import json +import shutil +import hashlib +import zipfile +import tempfile +import argparse +import subprocess +import urllib.request +from pathlib import Path + +OUTPUT = Path("msvc") # output folder + +# other architectures may work or may not - not really tested +HOST = "x64" # or x86 +TARGET = "x64" # or x86, arm, arm64 + +MANIFEST_URL = "https://aka.ms/vs/17/release/channel" + + +def download(url): + with urllib.request.urlopen(url) as res: + return res.read() + +def download_progress(url, check, name, f): + data = io.BytesIO() + with urllib.request.urlopen(url) as res: + total = int(res.headers["Content-Length"]) + size = 0 + while True: + block = res.read(1<<20) + if not block: + break + f.write(block) + data.write(block) + size += len(block) + perc = size * 100 // total + print(f"\r{name} ... {perc}%", end="") + print() + data = data.getvalue() + digest = hashlib.sha256(data).hexdigest() + if check.lower() != digest: + exit(f"Hash mismatch for f{pkg}") + return data + +# super crappy msi format parser just to find required .cab files +def get_msi_cabs(msi): + index = 0 + while True: + index = msi.find(b".cab", index+4) + if index < 0: + return + yield msi[index-32:index+4].decode("ascii") + +def first(items, cond): + return next(item for item in items if cond(item)) + + +### parse command-line arguments + +ap = argparse.ArgumentParser() +ap.add_argument("--show-versions", const=True, action="store_const", help="Show available MSVC and Windows SDK versions") +ap.add_argument("--accept-license", const=True, action="store_const", help="Automatically accept license") +ap.add_argument("--msvc-version", help="Get specific MSVC version") +ap.add_argument("--sdk-version", help="Get specific Windows SDK version") +ap.add_argument("--no-msvc", const=True, action="store_const", help="Skip download and installing of msvc") +ap.add_argument("--no-sdk", const=True, action="store_const", help="Skip download and installing of Windows SDK") +args = ap.parse_args() + +### get main manifest + +manifest = json.loads(download(MANIFEST_URL)) + + +### download VS manifest + +vs = first(manifest["channelItems"], lambda x: x["id"] == "Microsoft.VisualStudio.Manifests.VisualStudio") +payload = vs["payloads"][0]["url"] + +vsmanifest = json.loads(download(payload)) + + +### find MSVC & WinSDK versions + +packages = {} +for p in vsmanifest["packages"]: + packages.setdefault(p["id"].lower(), []).append(p) + +msvc = {} +sdk = {} + +for pid,p in packages.items(): + if pid.startswith("Microsoft.VisualStudio.Component.VC.".lower()) and pid.endswith(".x86.x64".lower()): + pver = ".".join(pid.split(".")[4:6]) + if pver[0].isnumeric(): + msvc[pver] = pid + elif pid.startswith("Microsoft.VisualStudio.Component.Windows10SDK.".lower()) or \ + pid.startswith("Microsoft.VisualStudio.Component.Windows11SDK.".lower()): + pver = pid.split(".")[-1] + if pver.isnumeric(): + sdk[pver] = pid + +if args.show_versions: + print("MSVC versions:", " ".join(sorted(msvc.keys()))) + print("Windows SDK versions:", " ".join(sorted(sdk.keys()))) + exit(0) + +install_sdk = not args.no_sdk +install_msvc = not args.no_msvc +if args.no_sdk and args.no_msvc: + exit() + +msvc_ver = args.msvc_version or max(sorted(msvc.keys())) +sdk_ver = args.sdk_version or max(sorted(sdk.keys())) + +info_line = "Downloading" +if install_msvc: + if msvc_ver in msvc: + msvc_pid = msvc[msvc_ver] + msvc_ver = ".".join(msvc_pid.split(".")[4:-2]) + else: + exit(f"Unknown MSVC version: f{args.msvc_version}") + info_line += f" MSVC v{msvc_ver}" + +if install_sdk: + if sdk_ver in sdk: + sdk_pid = sdk[sdk_ver] + else: + exit(f"Unknown Windows SDK version: f{args.sdk_version}") + info_line += f" Windows SDK v{sdk_ver}" + +print(info_line) + + +### agree to license + +tools = first(manifest["channelItems"], lambda x: x["id"] == "Microsoft.VisualStudio.Product.BuildTools") +resource = first(tools["localizedResources"], lambda x: x["language"] == "en-us") +license = resource["license"] + +if not args.accept_license: + accept = input(f"Do you accept Visual Studio license at {license} [Y/N] ? ") + if not accept or accept[0].lower() != "y": + exit(0) + +OUTPUT.mkdir(exist_ok=True) +total_download = 0 + +### download MSVC + +if install_msvc: + msvc_packages = [ + # MSVC binaries + f"microsoft.vc.{msvc_ver}.tools.host{HOST}.target{TARGET}.base", + f"microsoft.vc.{msvc_ver}.tools.host{HOST}.target{TARGET}.res.base", + # MSVC headers + f"microsoft.vc.{msvc_ver}.crt.headers.base", + # MSVC libs + f"microsoft.vc.{msvc_ver}.crt.{TARGET}.desktop.base", + f"microsoft.vc.{msvc_ver}.crt.{TARGET}.store.base", + # MSVC runtime source + f"microsoft.vc.{msvc_ver}.crt.source.base", + # ASAN + f"microsoft.vc.{msvc_ver}.asan.headers.base", + f"microsoft.vc.{msvc_ver}.asan.{TARGET}.base", + # MSVC redist + #f"microsoft.vc.{msvc_ver}.crt.redist.x64.base", + ] + + for pkg in msvc_packages: + p = first(packages[pkg], lambda p: p.get("language") in (None, "en-US")) + for payload in p["payloads"]: + with tempfile.TemporaryFile() as f: + data = download_progress(payload["url"], payload["sha256"], pkg, f) + total_download += len(data) + with zipfile.ZipFile(f) as z: + for name in z.namelist(): + if name.startswith("Contents/"): + out = OUTPUT / Path(name).relative_to("Contents") + out.parent.mkdir(parents=True, exist_ok=True) + out.write_bytes(z.read(name)) + + +### download Windows SDK + +if install_sdk: + sdk_packages = [ + # Windows SDK tools (like rc.exe & mt.exe) + f"Windows SDK for Windows Store Apps Tools-x86_en-us.msi", + # Windows SDK headers + f"Windows SDK for Windows Store Apps Headers-x86_en-us.msi", + f"Windows SDK Desktop Headers x86-x86_en-us.msi", + # Windows SDK libs + f"Windows SDK for Windows Store Apps Libs-x86_en-us.msi", + f"Windows SDK Desktop Libs {TARGET}-x86_en-us.msi", + # CRT headers & libs + f"Universal CRT Headers Libraries and Sources-x86_en-us.msi", + # CRT redist + #"Universal CRT Redistributable-x86_en-us.msi", + ] + + with tempfile.TemporaryDirectory() as d: + dst = Path(d) + + sdk_pkg = packages[sdk_pid][0] + sdk_pkg = packages[first(sdk_pkg["dependencies"], lambda x: True).lower()][0] + + msi = [] + cabs = [] + + # download msi files + for pkg in sdk_packages: + payload = first(sdk_pkg["payloads"], lambda p: p["fileName"] == f"Installers\\{pkg}") + msi.append(dst / pkg) + with open(dst / pkg, "wb") as f: + data = download_progress(payload["url"], payload["sha256"], pkg, f) + total_download += len(data) + cabs += list(get_msi_cabs(data)) + + # download .cab files + for pkg in cabs: + payload = first(sdk_pkg["payloads"], lambda p: p["fileName"] == f"Installers\\{pkg}") + with open(dst / pkg, "wb") as f: + download_progress(payload["url"], payload["sha256"], pkg, f) + + print("Unpacking msi files...") + + # run msi installers + for m in msi: + subprocess.check_call(["msiexec.exe", "/a", m, "/quiet", "/qn", f"TARGETDIR={OUTPUT.resolve()}"]) + + +### versions + +msvcv = "" +sdkv = "" + +if install_msvc: + msvcv = list((OUTPUT / "VC/Tools/MSVC").glob("*"))[0].name + +if install_sdk: + sdkv = list((OUTPUT / "Windows Kits/10/bin").glob("*"))[0].name + +# place debug CRT runtime into MSVC folder (not what real Visual Studio installer does... but is reasonable) + +if install_msvc: + dst = str(OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{HOST}/{TARGET}") + + pkg = "microsoft.visualcpp.runtimedebug.14" + dbg = packages[pkg][0] + payload = first(dbg["payloads"], lambda p: p["fileName"] == "cab1.cab") + try: + with tempfile.TemporaryFile(suffix=".cab", delete=False) as f: + data = download_progress(payload["url"], payload["sha256"], pkg, f) + total_download += len(data) + subprocess.check_call(["expand.exe", f.name, "-F:*", dst], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + finally: + os.unlink(f.name) + +# place the folders under the Redist folder in the SDK under a versioned folder to allow other versions to be installed + +if install_sdk: + redist_dir = OUTPUT / "Windows Kits/10/Redist" + redist_versioned_dir = redist_dir / f'{sdkv}' + + if not os.path.exists(redist_versioned_dir): + os.makedirs(redist_versioned_dir) + + for file_name in os.listdir(redist_dir): + if not file_name.startswith('10.0.'): # Simple heuristic + shutil.move((redist_dir / file_name), redist_versioned_dir) + +### cleanup + +shutil.rmtree(OUTPUT / "Common7", ignore_errors=True) +if install_msvc: + for f in ["Auxiliary", f"lib/{TARGET}/store", f"lib/{TARGET}/uwp"]: + shutil.rmtree(OUTPUT / "VC/Tools/MSVC" / msvcv / f) +for f in OUTPUT.glob("*.msi"): + f.unlink() +if install_sdk: + for f in ["Catalogs", "DesignTime", f"bin/{sdkv}/chpe", f"Lib/{sdkv}/ucrt_enclave"]: + shutil.rmtree(OUTPUT / "Windows Kits/10" / f, ignore_errors=True) +for arch in ["x86", "x64", "arm", "arm64"]: + if arch != TARGET: + if install_msvc: + shutil.rmtree(OUTPUT / "VC/Tools/MSVC" / msvcv / f"bin/Host{arch}", ignore_errors=True) + if install_sdk: + shutil.rmtree(OUTPUT / "Windows Kits/10/bin" / sdkv / arch) + shutil.rmtree(OUTPUT / "Windows Kits/10/Lib" / sdkv / "ucrt" / arch) + shutil.rmtree(OUTPUT / "Windows Kits/10/Lib" / sdkv / "um" / arch) + + +### setup.bat + +if install_msvc and install_sdk: + SETUP = f"""@echo off + +set MSVC_VERSION={msvcv} +set MSVC_HOST=Host{HOST} +set MSVC_ARCH={TARGET} +set SDK_VERSION={sdkv} +set SDK_ARCH={TARGET} + +set MSVC_ROOT=%~dp0VC\\Tools\\MSVC\\%MSVC_VERSION% +set SDK_INCLUDE=%~dp0Windows Kits\\10\\Include\\%SDK_VERSION% +set SDK_LIBS=%~dp0Windows Kits\\10\\Lib\\%SDK_VERSION% + +set VCToolsInstallDir=%MSVC_ROOT%\\ +set PATH=%MSVC_ROOT%\\bin\\%MSVC_HOST%\\%MSVC_ARCH%;%~dp0Windows Kits\\10\\bin\\%SDK_VERSION%\\%SDK_ARCH%;%~dp0Windows Kits\\10\\bin\\%SDK_VERSION%\\%SDK_ARCH%\\ucrt;%PATH% +set INCLUDE=%MSVC_ROOT%\\include;%SDK_INCLUDE%\\ucrt;%SDK_INCLUDE%\\shared;%SDK_INCLUDE%\\um;%SDK_INCLUDE%\\winrt;%SDK_INCLUDE%\\cppwinrt +set LIB=%MSVC_ROOT%\\lib\\%MSVC_ARCH%;%SDK_LIBS%\\ucrt\\%SDK_ARCH%;%SDK_LIBS%\\um\\%SDK_ARCH% + """ + (OUTPUT / "setup.bat").write_text(SETUP) + +if install_msvc: + MSVC_SCRIPT = f"""@echo off + +set MSVC_VERSION={msvcv} +set MSVC_HOST=Host{HOST} +set MSVC_ARCH={TARGET} +set MSVC_ROOT=%~dp0VC\\Tools\\MSVC\\%MSVC_VERSION% + +set VCToolsInstallDir=%MSVC_ROOT%\\;%VCToolsInstallDir% +set PATH=%MSVC_ROOT%\\bin\\%MSVC_HOST%\\%MSVC_ARCH%;%PATH% +set INCLUDE=%MSVC_ROOT%\\include;%INCLUDE% +set LIB=%MSVC_ROOT%\\lib\\%MSVC_ARCH%;%LIB% +""" + (OUTPUT / f"msvc-{msvcv}.bat").write_text(MSVC_SCRIPT) + +if install_sdk: + WIN10_SDK_SCRIPT = f"""@echo off + +set SDK_VERSION={sdkv} +set SDK_ARCH={TARGET} +set SDK_INCLUDE=%~dp0Windows Kits\\10\\Include\\%SDK_VERSION% +set SDK_LIBS=%~dp0Windows Kits\\10\\Lib\\%SDK_VERSION% + +set PATH=%~dp0Windows Kits\\10\\bin\\%SDK_VERSION%\\%SDK_ARCH%;%~dp0Windows Kits\\10\\bin\\%SDK_VERSION%\\%SDK_ARCH%\\ucrt;%PATH% +set INCLUDE=%SDK_INCLUDE%\\ucrt;%SDK_INCLUDE%\\shared;%SDK_INCLUDE%\\um;%SDK_INCLUDE%\\winrt;%SDK_INCLUDE%\\cppwinrt +set LIB=%SDK_LIBS%\\ucrt\\%SDK_ARCH%;%SDK_LIBS%\\um\\%SDK_ARCH% + """ + (OUTPUT / f"win-sdk-{sdkv}.bat").write_text(WIN10_SDK_SCRIPT) + +print(f"Total downloaded: {total_download>>20} MB") +print("Done!") diff --git a/win_setup.py b/win_setup.py new file mode 100644 index 0000000..14babde --- /dev/null +++ b/win_setup.py @@ -0,0 +1,279 @@ +import devenver +import pprint +import subprocess +import sys +import pathlib +import os +import shutil +import tempfile +import devenver_manifest +import urllib.request + +def git_clone(install_dir, git_exe, url, commit_hash): + devenver.lprint(f"Git clone {url} to {install_dir}", level=0) + # Clone repository if it does not exist + if not os.path.exists(install_dir): + devenver.lprint(f"Cloning to {install_dir}") + subprocess.run(f"{git_exe} clone {url} {install_dir}") + + # Determine current git hash + result = subprocess.run(f"{git_exe} rev-parse --short HEAD", + cwd=install_dir, + capture_output=True) + curr_commit_hash = result.stdout.decode("utf-8").strip() + + # Checkout correct target of Odin + if curr_commit_hash != commit_hash: + subprocess.run(f"{git_exe} checkout master", cwd=install_dir) + subprocess.run(f"{git_exe} pull origin master", cwd=install_dir) + subprocess.run(f"{git_exe} checkout {commit_hash}", cwd=install_dir) + +# Run DEVenver, installing the portable apps +# ------------------------------------------------------------------------------ +user_app_list = devenver_manifest.get_manifest() +installed_apps = devenver.run(user_app_list) + +# Install MSVC +# ------------------------------------------------------------------------------ +devenver.print_header("Install MSVC & Windows 10 SDK") +msvc_script = pathlib.Path(devenver.script_dir, "win_portable_msvc.py") +msvc_version = "14.34" +win10_sdk_version = "22621" + +msvc_install_dir = devenver.base_install_dir / "msvc" + +# Basic heuristic to see if we"ve already installed the MSVC/SDK version +msvc_installed = False +win10_sdk_installed = False + +msvc_find_test_dir = msvc_install_dir / "VC/Tools/MSVC" +win10_sdk_find_test_dir = msvc_install_dir / "Windows Kits/10" + +if os.path.exists(msvc_find_test_dir): + for file_name in os.listdir(msvc_find_test_dir): + msvc_installed = file_name.startswith(msvc_version) + if msvc_installed == True: + devenver.lprint(f"MSVC {msvc_version} install detected (skip download) in {msvc_find_test_dir}\\{file_name}") + break +if not msvc_installed: + devenver.lprint(f"MSVC {msvc_version} install not detected (need to download) in {msvc_find_test_dir}") + +if os.path.exists(win10_sdk_find_test_dir): + for file_name in os.listdir(win10_sdk_find_test_dir / "bin"): + # Check if directory contains version substring, 22621, e.g. "10.0.22621.0" + win10_sdk_installed = file_name.count(win10_sdk_version) > 0 + if win10_sdk_installed == True: + install_locations = f"{win10_sdk_find_test_dir}\\*\\{file_name}" + devenver.lprint(f"Windows 10 SDK {win10_sdk_version} install detected (skip download) in {install_locations}") + break +if not win10_sdk_installed: + devenver.lprint(f"Windows 10 SDK {win10_sdk_version} not detected (need to download) in {win10_sdk_find_test_dir}") + +# Install MSVC +if msvc_installed == False or win10_sdk_installed == False: + with tempfile.TemporaryDirectory() as temp_dir: + + # Invoke the MSVC script to download MSVC to disk + command = f"'{sys.executable}' '{msvc_script}' --accept-license" + line = "Invoking MSVC script to install" + if msvc_installed: + command += " --no-msvc" + else: + command += f" --msvc-version {msvc_version}" + line += f" MSVC {msvc_version}" + + if win10_sdk_installed: + command += " --no-sdk" + else: + command += f" --sdk-version {win10_sdk_version}" + line += f" Windows 10 SDK {win10_sdk_version}" + + devenver.lprint(line) + devenver.lprint(f"Command: {command}") + subprocess.run(command, cwd=temp_dir) + + # Merge the download MSVC installation to our unified install dir + temp_msvc_dir = pathlib.Path(temp_dir, "msvc") + for src_dir, dirs, files in os.walk(temp_msvc_dir): + install_dir = src_dir.replace(str(temp_msvc_dir), str(msvc_install_dir), 1) + if not os.path.exists(install_dir): + os.makedirs(install_dir) + for file_ in files: + src = os.path.join(src_dir, file_) + dest = os.path.join(install_dir, file_) + if os.path.exists(dest): + if os.path.samefile(src, dest): + continue + os.remove(dest) + shutil.move(src, install_dir) + + devenver.lprint(f"MSVC {msvc_version} Windows 10 SDK {win10_sdk_version} installed: {msvc_install_dir}") + +# Install apps dependent on Git +# ------------------------------------------------------------------------------ +devenver.print_header("Install apps that rely on Git") +git_exe = installed_apps["Git"][0]['exe_path'] + +# Clink Completions +# ------------------------------------------------------------------------------ +clink_git_hash = "fa18736" +clink_install_dir = pathlib.Path(devenver.base_install_dir, "clink-completions") +git_clone(install_dir=clink_install_dir, + git_exe=git_exe, + url="https://github.com/vladimir-kotikov/clink-completions", + commit_hash=clink_git_hash) + +# Odin +# ------------------------------------------------------------------------------ +odin_git_hash = "9ae1bfb6" +odin_install_dir = pathlib.Path(devenver.base_install_dir, "Odin") +git_clone(install_dir=odin_install_dir, + git_exe=git_exe, + url="https://github.com/odin-lang/odin.git", + commit_hash=odin_git_hash) + +# TODO: We can't do this yet because the odin build requires a registry hack so +# that it knows where to find MSVC. + +# Build Odin +# subprocess.run(f"{git_exe} checkout {odin_git_hash}", +# cwd=odin_install_dir) + +# Install left-overs +# ------------------------------------------------------------------------------ +devenver.print_header("Install configuration files") + +# Copy init.vim to NVIM directory +internal_dir = pathlib.Path(os.path.dirname(os.path.abspath(__file__)), "Internal") +nvim_init_dir = pathlib.Path(os.path.expanduser("~"), "AppData", "Local", "nvim") +nvim_config_dest_path = nvim_init_dir / "init.vim" +nvim_config_src_path = internal_dir / "os_nvim_init.vim" + +devenver.lprint(f"Installing NVIM config to {nvim_config_dest_path}") +nvim_init_dir.mkdir(parents=True, exist_ok=True) +shutil.copy(nvim_config_src_path, nvim_config_dest_path) + +# Download vim.plug to NVIM init directory +nvim_plug_vim_dir = nvim_init_dir / "autoload" +nvim_plug_vim_path = nvim_plug_vim_dir / "plug.vim" +nvim_plug_vim_dir.mkdir(parents=True, exist_ok=True) +if not os.path.exists(nvim_plug_vim_path): + devenver.lprint(f"Installing NVIM plugin manager to {nvim_plug_vim_path}") + urllib.request.urlretrieve("https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim", + nvim_plug_vim_path) + +# Install wezterm configuration +wezterm_install_dir = installed_apps["WezTerm"][0]["install_dir"] +wezterm_exe_path = installed_apps["WezTerm"][0]["exe_path"] +wezterm_config_dest_path = wezterm_install_dir / "wezterm.lua" + +devenver.lprint(f"Installing WezTerm config to {wezterm_config_dest_path}") + +clink_install_dir = installed_apps["clink"][0]["install_dir"] +clink_exe_path = clink_install_dir.relative_to(devenver.base_install_dir) / "clink_x64.exe" +clink_exe_path_for_wezterm = str(clink_exe_path).replace("\\", "\\\\") + +wezterm_lua_buffer = f"""local wezterm = require 'wezterm'; + +local default_prog +local set_environment_variables = {{}} + +if wezterm.target_triple == "x86_64-pc-windows-msvc" then + + clink_exe = string.format("%s\\\\..\\\\..\\\\{clink_exe_path_for_wezterm}", wezterm.executable_dir) + devenv_bat = string.format("%s\\\\..\\\\..\\\\devenv.bat", wezterm.executable_dir) + msvc_bat = string.format("%s\\\\..\\\\..\\\\msvc\\\\setup.bat", wezterm.executable_dir) + + -- Taken from: https://wezfurlong.org/wezterm/shell-integration.html + -- Use OSC 7 as per the above example + set_environment_variables['prompt'] = + '$E]7;file://localhost/$P$E\\\\$E[32m$T$E[0m $E[35m$P$E[36m$_$G$E[0m ' + + -- use a more ls-like output format for dir + set_environment_variables['DIRCMD'] = '/d' + + default_prog = {{"cmd.exe", "/s", "/k", + clink_exe, "inject", "-q", + "&&", "call", devenv_bat, + "&&", "call", msvc_bat}} +end + +return {{ + font_size = 10.0, + color_scheme = "Peppermint", + default_prog = default_prog, + set_environment_variables = set_environment_variables, +}} +""" + +with open(wezterm_config_dest_path, "w") as file: + file.write(wezterm_lua_buffer) + +# Wezterm super terminal +wezterm_exe_rel_path = pathlib.Path(wezterm_exe_path).relative_to(devenver.base_install_dir) +wezterm_terminal_script_path = pathlib.Path(devenver.base_install_dir, "win_terminal.bat") +wezterm_terminal_script = f"""@echo off +setlocal EnableDelayedExpansion + +set working_dir= +if "%~1" neq "" ( + set working_dir=start --cwd "%~1" + set working_dir=!working_dir:\=/! +) + +if exist "%~dp0win_terminal_user_config.bat" call "%~dp0win_terminal_user_config.bat" +start "" /MAX "%~dp0{wezterm_exe_rel_path}" !working_dir! +""" + +devenver.lprint(f"Installing WezTerm terminal script to {wezterm_terminal_script_path}") +with open(wezterm_terminal_script_path, "w") as file: + file.write(wezterm_terminal_script) + +# Create Odin work-around scripts +# Odin uses J. Blow's Microsoft craziness SDK locator which relies on the +# registry. Here we inject the registry entry that the SDK locator checks for +# finding our portable MSVC installation. +win10_sdk_find_test_dir_reg_path = str(win10_sdk_find_test_dir).replace("\\", "\\\\") +odin_msvc_install_script = f"""Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots] +"KitsRoot10"="{win10_sdk_find_test_dir_reg_path}" +""" + +odin_msvc_uninstall_script = f"""Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots] +"KitsRoot10"=- +""" + +odin_msvc_install_script_path = devenver.base_install_dir / "odin_msvc_install_workaround.reg" +odin_msvc_uninstall_script_path = devenver.base_install_dir / "odin_msvc_uninstall_workaround.reg" + +devenver.lprint(f"Installing Odin MSVC workaround scripts", level=0) +devenver.lprint(f" - {odin_msvc_install_script_path}", level=1) +devenver.lprint(f" - {odin_msvc_uninstall_script_path}", level=1) + +with open(odin_msvc_install_script_path, "w") as file: + file.write(odin_msvc_install_script) + +with open(odin_msvc_uninstall_script_path, "w") as file: + file.write(odin_msvc_uninstall_script) + +# Add python-update bootstrap script +# TODO: If I'm using the terminal that this script generates it will lock the +# executable and Python cannot open the file for verifying the SHA256. + +python_exe = pathlib.Path(installed_apps["Python"][0]['exe_path']).relative_to(devenver.base_install_dir) +python_install_dir = pathlib.Path(installed_apps["Python"][0]['exe_path']).parent.relative_to(devenver.base_install_dir) +win_setup_script_path = pathlib.Path(devenver.script_dir, "win_setup.py") +manifest_script_path = pathlib.Path(devenver.script_dir, "devenver_manifest.py") + +bootstrap_setup_script = f"""@echo off +setlocal EnableDelayedExpansion +set PYTHONHOME=%~dp0{python_install_dir} +%~dp0{python_exe} {win_setup_script_path} --manifest-file {manifest_script_path} +pause +""" + +with open(devenver.base_install_dir / "upgrade_bootstrap.bat", "w") as file: + file.write(bootstrap_setup_script)