This repository was archived by the owner on Aug 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathpackage_compiler.livecodescript
More file actions
1835 lines (1577 loc) · 68.8 KB
/
package_compiler.livecodescript
File metadata and controls
1835 lines (1577 loc) · 68.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
script "PackageCompiler"
################################################################################
local sCurrentTempFolder
-- Create a new compiler object, represented as an array
command packageCompilerCreate @rCompiler
put empty into rCompiler
end packageCompilerCreate
-- Destroy the given compiler object, ensuring any intermediate
-- resources are deleted.
command packageCompilerDestroy @self
put empty into self
end packageCompilerDestroy
-- Set compiler options:
-- temporary: the folder to use for temporary files
-- sign.certificate: the code-signing certificate to use
-- sign.privatekey: the code-signing private key to use
-- sign.password: the passphrase to use to decode the private key
command packageCompilerConfigure @self, pOption, pValue
put pValue into self["options"][pOption]
end packageCompilerConfigure
-- Add a source folder to the compiler. Files from these
-- sources are referenced using:
-- pTag ':' pFile
-- A source can also be 'based on' another source. If the 'based on' parameter is set to another
-- source, then any files present in the base source that are used from the new source will be
-- derived via bsdiff.
command packageCompilerConfigureSource @self, pTag, pFolder, pBasedOn
abstractCanonicalize pFolder
put pFolder into self["sources"][pTag]
compilerProgress self, "Added source for '" & pTag & "': '" & pFolder & "'"
-- If the folder doesn't exist, but a prefix of the folder path is a file
-- then register that prefix as abstract.
if there is no folder pFolder then
set the itemDelimiter to slash
repeat with i = the number of items in pFolder - 1 down to 2
if there is a file (item 1 to i of pFolder) then
abstractAddPath item 1 to i of pFolder
exit repeat
end if
end repeat
set the itemDelimiter to comma
end if
if pBasedOn is not empty then
throw "not implemented"
end if
if pBasedOn is not pTag then
put pBasedOn into self["base_sources"][pTag]
end if
end packageCompilerConfigureSource
-- Configure a top-level variable in the compiler. These
-- variables are substituted into [[...]] clauses in the commands
command packageCompilerConfigureVariable @self, pName, pValue
put pValue into self["default context"][pName]
end packageCompilerConfigureVariable
-- Configure a callback for status messages.
command packageCompilerSetReportCallback @self, pObject, pHandler
put pObject into self["report.target"]
put pHandler into self["report.handler"]
end packageCompilerSetReportCallback
-- Parse the given script
command packageCompilerParse @self, pScript
try
compilerParse self, pScript
catch tException
end try
if tException is not empty and the number of lines of tException is not 1 then
throw tException
end if
return tException
end packageCompilerParse
-- Build the given installer component
command packageCompilerBuild @self, pInstaller, pOutput
-- The files array will be an array with keys of the form:
-- <process>':'<abs path>
-- Mapping to the item name in the zip archive. Here process is what (if anything) should
-- happen to the source file before being placed into the zip:
-- none: use it as is
-- text: map any line endings to appropriate form for target platform
-- slim-i386: extract the i386 part of a mac os x exe, and strip it
-- slim-ppc: extract the ppc part of a mac os x exe, and strip it
--
-- The manifest will be a return-delimited list of entries detailing where files in the zip are
-- to be placed:
-- <type> [tab] <zip item> [tab] <target>
-- Here <type> is either file, folder or exe. For folders, the <zip item> is ignored. For exes
-- a 'chmod' is done to the resulting file if required for that platform.
--
-- The groups key is used to list items such as externals and dbdrivers that
-- need lists generated. It is modified by the declare/emit commands.
--
put empty into self["files"]
put empty into self["manifest"]
put empty into self["stack"]
put empty into self["warnings"]
put self["default context"] into self["context"]
put pOutput into self["output"]
try
put self["options"]["temporary"] into sCurrentTempFolder
compilerRun self, pInstaller
catch tException
end try
-- If the exception is not one of ours, then throw it. Otherwise we return.
if tException is not empty then
if the number of lines of tException is not 1 then
throw tException
end if
return tException
end if
local tArchive, tTmpFiles
-- TODO: Normalize manifest by adding missing folders
compilerNormalizeManifest self
-- Open a zip type archive
compilerProgress self, "Opening zip archive '" & pOutput & "'"
archiveOpenZip tArchive, pOutput
-- Compute the list of used files
local tUsedFiles
repeat for each element tFile in self["files"]
put true into tUsedFiles[tFile]
end repeat
-- Process the manifest to eliminate any patches that rely on unused files
local tManifest
set the itemDelimiter to tab
repeat for each line tLine in self["manifest"]
if item 1 of tLine is among the words of "file executable" and \
item 4 of tLine is not empty then
if not tUsedFiles[item 4 of tLine] then
delete item 4 of tLine
end if
end if
put tLine & return after tManifest
end repeat
set the itemDelimiter to comma
-- Add the manifest file, this simply goes at the root
archiveAddItemWithData tArchive, "manifest.txt", compilerProcessText(self, tManifest)
-- Now loop through all the files
local tSourceFiles, tItemFiles
repeat for each key tItem in self["files"]
local tItemProcess, tItemFile
set the itemDelimiter to ":"
put item 1 of tItem into tItemProcess
put item 2 to -1 of tItem into tItemFile
set the itemDelimiter to comma
-- Fetch the file to base this item on (if any). Notice that we ignore base references
-- if the base file itself is not used directly.
-- TODO: Make sure this resolves to the final 'base' file since we don't support
-- cascaded patches yet.
local tBaseItem, tBaseItemProcess, tBaseItemFile
if self["base_files"][tItem] is not empty and self["files"][self["base_files"][tItem]] is not empty then
put self["base_files"][tItem] into tBaseItem
set the itemDelimiter to ":"
put item 1 of tBaseItem into tBaseItemProcess
put item 2 to -1 of tBaseItem into tBaseItemFile
set the itemDelimiter to comma
else
put empty into tBaseItem
put empty into tBaseItemFile
end if
-- First check to see if the item has been processed.
local tDerivedFile
if tItemFiles[tItem] is empty then
put compilerProcessItem(self, tItemProcess, tItemFile) into tDerivedFile
put tDerivedFile into tItemFiles[tItem]
if tItemFile is not tDerivedFile then
put tDerivedFile & return after tTmpFiles
end if
end if
-- Now check to see if the base item, if not empty, has been processed
if tBaseItem is not empty and tItemFiles[tBaseItem] is empty then
put compilerProcessItem(self, tBaseItemProcess, tBaseItemFile) into tDerivedFile
put tDerivedFile into tItemFiles[tBaseItem]
if tBaseItemFile is not tDerivedFile then
put tDerivedFile & return after tTmpFiles
end if
end if
-- Finally either compute a diff, or just add the file directly
if tBaseItem is empty then
archiveAddItemWithFile tArchive, self["files"][tItem], tItemFiles[tItem]
else if tItemFiles[tBaseItem] is not empty and tItemFiles[tItem] is not empty then
put compilerMakeTemporaryFile(self, "diff") into tDerivedFile
put tDerivedFile & return after tTmpFiles
compilerProgress self, "Diffing '" & tItemFiles[tBaseItem] &"' and '" & tItemFiles[tItem] & "'"
_internal bsdiff abstractPinFile(tItemFiles[tBaseItem]) to abstractPinFile(tItemFiles[tItem]) into tDerivedFile
archiveAddItemWithFile tArchive, self["files"][tItem], tDerivedFile
end if
end repeat
-- Now loop through the derived files
repeat for each element tDerivedItem in self["derived"]
archiveAddItemWithData tArchive, tDerivedItem["name"], compilerProcessText(self, tDerivedItem["data"])
end repeat
-- Close and create the final archive
local tError
compilerProgress self, "Closing zip archive"
archiveClose tArchive
if the result is not empty then
put "error: archive: " & the result into tError
end if
-- Remove any temporary files generated in the above process
if self["options"]["temporary"] is empty then
repeat for each line tFile in tTmpFiles
delete file tFile
end repeat
end if
-- Return the error, if any
return tError
end packageCompilerBuild
function packageCompilerGetWarnings @self
return self["warnings"]
end packageCompilerGetWarnings
################################################################################
-- Parsing builds a tree of components, and flattens compound commands
-- such as 'with' and 'into'.
private command compilerParse @self, pScript
-- This stores the current list of contexts, one per line. The currently
-- supported contexts are:
-- installer:
-- a top-level aggregation of components
-- component:
-- a collection of commands that can be 'included'
-- with:
-- changes the value of a variable for a given set of commands
-- into:
-- sets a default install target for a given set of commands
--
local tContext
put empty into tContext
local tComponentName, tComponentType, tComponentCommands, tComponentCommandCount
put empty into tComponentName
put empty into tComponentType
put empty into tComponentCommands
put 0 into tComponentCommandCount
local tLineNumber
put 0 into tLineNumber
repeat for each line tLine in pScript
-- Increment the line number
add 1 to tLineNumber
-- Ignore comment lines
if word 1 of tLine begins with "//" or \
word 1 of tLine begins with "--" or \
word 1 of tLine begins with "#" then
next repeat
end if
-- Ignore empty lines
if word 1 to -1 of tLine is empty then
next repeat
end if
-- Determine the depth by counting tab characters
local tDepth
put 0 into tDepth
repeat while the first char of tLine is tab
add 1 to tDepth
delete char 1 of tLine
end repeat
-- If the depth is greater than the current context depth, it is an error
if tDepth > the number of lines of tContext then
compilerSyntaxError self, "Invalid depth for command", tLineNumber
end if
-- Pop context until we reach the appropriate depth
repeat while tDepth < the number of lines of tContext
delete the last line of tContext
put "pop" into tComponentCommands[tComponentCommandCount]["type"]
put tLine into tComponentCommands[tComponentCommandCount]["line content"]
put tLineNumber into tComponentCommands[tComponentCommandCount]["line"]
add 1 to tComponentCommandCount
end repeat
-- If we are back at top-level context, we are done processing the current
-- installer/component, so enter the command list into self
if tContext is empty then
put tComponentCommands into self[tComponentType][tComponentName]
put empty into tComponentName
put empty into tComponentType
put empty into tComponentCommands
put 0 into tComponentCommandCount
end if
-- Strip any whitespace from the line
put word 1 to -1 of tLine into tLine
-- Check to see if depth is appropriate for the command
if word 1 of tLine is among the items of "installer,component" then
if tContext is not empty then
compilerSyntaxError self, "'Command only allowed at top-level", tLineNumber
end if
else
if tContext is empty then
compilerSyntaxError self, "Command not allowed at top-level", tLineNumber
end if
end if
-- Now process the commands, depending on the first word
local tCommand, tNeedPush
put false into tNeedPush
put empty into tCommand
put tLine into tCommand["line content"]
put tLineNumber into tCommand["line"]
switch word 1 of tLine
case "installer"
case "component"
-- Ensure there are only two words
if the number of words of tLine is not 2 then
compilerSyntaxError self, "Invalid command, syntax is '" && (word 1 of tLine) && "<name>'", tLineNumber
end if
put word 1 of tLine & return after tContext
put word 1 of tLine & "s" into tComponentType
put wordToName(word 2 of tLine) into tComponentName
-- Check that the component doesn't already exist
if tComponentName is among the keys of self[tComponentType][tComponentName] then
compilerSyntaxError self, "Component already '" & tComponentName & "' already declared", tLineNumber
end if
-- Starting a new component means an implicit push
put true into tNeedPush
-- And resetting of the RootFolder
put "set" into tCommand["type"]
put "RootFolder" into tCommand["variable"]
put empty into tCommand["value"]
break
case "additional"
-- Ensure that the following word is "manifest" and that there is
-- a final word containing the file to use
if the number of words of tLine is not 3 or word 2 of tLine is not "manifest" then
compilerSyntaxError self, "Invalid command, syntax is 'additional manifest <file>'", tLineNumber
end if
-- Get the manifest file name and parse it too
local tExtraManifest
local tExtraManifestName
put word 3 of tLine into tExtraManifestName
if char 1 of tExtraManifestName is quote and char -1 of tExtraManifestName is quote then
put char 2 to -2 of tExtraManifestName into tExtraManifestName
end if
put url ("file:" & builderRepoFolder() & slash & tExtraManifestName) into tExtraManifest
if the result is not empty then
compilerSyntaxError self, "Could not open additional manifest" && builderRepoFolder() & slash & tExtraManifestName, tLineNumber
end if
try
compilerParse self, tExtraManifest
catch tException
-- Rethrow with some additional context information
throw "In additional manifest" && tExtraManifestName & ":" && tException
end try
-- Note that no executable commands are added beyond those that
-- were added from the manifest file that was just included
put "nop" into tCommand["type"]
break
case "include"
-- Ensure that there are two words
if the number of words of tLine is not 2 then
compilerSyntaxError self, "Invalid command, syntax is 'include <name>'", tLineNumber
end if
-- Add the command
put "include" into tCommand["type"]
put wordToName(word 2 of tLine) into tCommand["name"]
break
case "set"
-- Ensure that there are four words and the form is correct
if the number of words of tLine is not 4 or \
word 3 of tLine is not "to" then
compilerSyntaxError self, "Invalid command, syntax is 'set <var> is <value>'", tLineNumber
end if
-- Add the command
put "set" into tCommand["type"]
put wordToName(word 2 of tLine) into tCommand["variable"]
put wordToName(word 4 of tLine) into tCommand["value"]
break
case "codesign"
-- Ensure that there is only one word
if the number of words of tLine is not 1 then
compilerSyntaxError self, "Invalid command, syntax is 'codesign'", tLineNumber
end if
-- This command places a new context
put "with" & return after tContext
-- It also implies an implicit push
put true into tNeedPush
-- Otherwise it is just a special case of 'set'
put "set" into tCommand["type"]
put "SignExecutables" into tCommand["variable"]
put "true" into tCommand["value"]
break
case "with"
-- Ensure that there are 5 words, and the form is correct
if the number of words of tLine is not 5 or \
word 3 of tLine is not "as" or \
word 5 of tLine is not "do" then
compilerSyntaxError self, "Invalid command, syntax is 'with <var> as <value> do'", tLineNumber
end if
-- This command places a new context
put "with" & return after tContext
-- It also implies an implicit push
put true into tNeedPush
-- Otherwise, its just a set
put "set" into tCommand["type"]
put wordToName(word 2 of tLine) into tCommand["variable"]
put wordToName(word 4 of tLine) into tCommand["value"]
break
case "into"
-- Ensure that there are 3 words and the form is correct
if the number of words of tLine is not 3 or \
word 3 of tLine is not "place" then
compilerSyntaxError self, "Invalid command, syntax is 'into <folder> place'", tLineNumber
end if
-- This command places a new context
put "into" & return after tContext
-- It also implies an implicit push
put true into tNeedPush
-- Otherwise its just a set of the RootFolder variable
put "set" into tCommand["type"]
put "RootFolder" into tCommand["variable"]
put wordToName(word 2 of tLine) into tCommand["value"]
break
case "executable"
case "stack"
case "textfile"
case "file"
case "folder"
case "rfolder"
-- Ensure that there are either 2 words or 4 words, and the form is correct
if the number of words of tLine is not among the items of "2,4,6" or \
the number of words of tLine is 4 and word 3 of tLine is not "as" or \
the number of words of tLine is 6 and word 5 of tLine is not "base" then
compilerSyntaxError self, "Invalid command, syntax is '" && word 1 of tLine && "<source> [ as <target> [ base <base-source> ] ]'", tLineNumber
end if
-- Put together the command
put "copy" into tCommand["type"]
put word 1 of tLine into tCommand["class"]
put wordToName(word 2 of tLine) into tCommand["source"]
if the number of words of tLine >= 4 then
put wordToName(word 4 of tLine) into tCommand["target"]
end if
if the number of words of tLine >= 6 then
put wordToName(word 6 of tLine) into tCommand["source-base"]
end if
break
case "emit"
local tClass
put wordToName(word 2 of tLine) into tClass
local tMaxWords
switch tClass
case "externals"
case "dbdrivers"
delete the last char of tClass
put 4 into tMaxWords
break
case "string"
case "variable"
put 5 into tMaxWords
put wordToName(word 3 of tLine) into tCommand["name"]
break
default
put 0 into tMaxWords
end switch
if the number of words of tLine is not tMaxWords or \
word -2 of tLine is not "to" then
compilerSyntaxError self, "Invalid command, syntax is 'emit <class> [{<variable> | <string>}] to <target>'", tLineNumber
end if
put "emit" into tCommand["type"]
put tClass into tCommand["class"]
put wordToName(word -1 of tLine) into tCommand["target"]
break
case "declare"
if the number of words of tLine is not 5 or \
word 2 of tLine is not among the items of "external,dbdriver" or \
word 4 of tLine is not "using" then
compilerSyntaxError self, "Invalid command, syntax is 'declare external <name> using <filelist>'", tLineNumber
end if
put "declare" into tCommand["type"]
put wordToName(word 2 of tLine) into tCommand["class"]
put wordToName(word 3 of tLine) into tCommand["name"]
put wordToName(word 5 of tLine) into tCommand["files"]
break
case "if"
case "ifnot"
if not (the number of words of tLine is 5 and word 3 of tLine is "is" and word 5 of tLine is "then") and \
not (the number of words of tLine is 4 and word 3 of tLine is "exists" and word 4 of tLine is "then") then
compilerSyntaxError self, "Invalid command, syntax is 'if[not] <name> ( is <name> | exists ) then'", tLineNumber
end if
-- This command places a new context
put "if" & return after tContext
-- It also implies an implicit push
put true into tNeedPush
-- Add the command
put "if" into tCommand["type"]
if word 1 of tLine is "ifnot" then
put true into tCommand["negated"]
end if
put wordToName(word 2 of tLine) into tCommand["left"]
if the number of words of tLine is 5 then
put wordToName(word 4 of tLine) into tCommand["right"]
end if
break
case "shortcut"
if not (the number of words of tLine is 4 and word 3 of tLine is "to") and \
not the number of words of tLine is 2 then
compilerSyntaxError self, "Invalid command, syntax is 'shortcut <name> [ to <target> ]'", tLineNumber
end if
put "shortcut" into tCommand["type"]
put wordToName(word 2 of tLine) into tCommand["source"]
if the number of words of tLine is 4 then
put wordToName(word 4 of tLine) into tCommand["target"]
end if
break
case "place"
if the number of words of tLine is not 4 or \
word 2 of tLine is not among the items of "record,uninstaller" or \
word 3 of tLine is not "at" then
compilerSyntaxError self, "Invalid command, syntax is 'place (record|uninstaller) at <target>'", tLineNumber
end if
put "place" into tCommand["type"]
put word 2 of tLine into tCommand["class"]
put wordToName(word 4 of tLine) into tCommand["target"]
break
case "register"
if not (the number of words of tLine is 3 and word 2 of tLine is "key") and \
not (the number of words of tLine is 5 and word 2 of tLine is among the words of "key value" and word 4 of tLine is "as") and \
not (the number of words of tLine is 7 and word 2 of tLine is "value" and word 4 of tLine is "of" and word 6 of tLine is "as") and \
not (the number of words of tLine is 6 and word 2 of tLine is among the words of "key value" and word 4 of tLine is "as" and word 5 of tLine is among the words of "string path number") and \
not (the number of words of tLine is 8 and word 2 of tLine is "value" and word 4 of tLine is "of" and word 6 of tLine is "as" and word 7 of tLine is among the words of "string path number") then
compilerSyntaxError self, "Invalid command, syntax is 'register (value | key) <key_or_value> [ of <key> ] [ as <value> ]'", tLineNumber
end if
put "register" into tCommand["type"]
put word 2 of tLine into tCommand["class"]
put wordToName(word 3 of tLine) into tCommand["target"]
local tRegistryValue, tRegistryType
if the number of words of tLine is 5 then
put word 5 of tLine into tRegistryValue
put empty into tRegistryType
else if the number of words of tLine is 6 then
put word 6 of tLine into tRegistryValue
put word 5 of tLine into tRegistryType
else if the number of words of tLine is 7 then
put word 7 of tLine into tRegistryValue
put empty into tRegistryType
put wordToName(word 5 of tLine) into tCommand["parent"]
else if the number of words of tLine is 8 then
put word 8 of tLine into tRegistryValue
put word 7 of tLine into tRegistryType
put wordToName(word 5 of tLine) into tCommand["parent"]
end if
if tRegistryType is empty then
if char 1 of tRegistryValue is quote or tRegistryValue is not an integer then
put "string" into tCommand["value_type"]
put wordToName(tRegistryValue) into tCommand["value"]
else
put "number" into tCommand["value_type"]
put tRegistryValue into tCommand["value"]
end if
else
put tRegistryType into tCommand["value_type"]
put wordToName(tRegistryValue) into tCommand["value"]
end if
break
case "rename"
if the number of words of tLine is not 4 or word 3 of tLine is not "to" then
compilerSyntaxError self, "Invalid command, syntax is 'rename <source> to <target>'", tLineNumber
end if
put "rename" into tCommand["type"]
put wordToName(word 2 of tLine) into tCommand["source"]
put wordToName(word 4 of tLine) into tCommand["target"]
break
case "desktop"
if the number of words of tLine is not 5 then
compilerSyntaxError self, "Invalid command, syntax is 'desktop (application | icon) <source> as <target>'", tLineNumber
end if
put "desktop" into tCommand["type"]
put word 2 of tLine into tCommand["class"]
put wordToName(word 3 of tLine) into tCommand["source"]
put wordToName(word 5 of tLine) into tCommand["target"]
break
default
compilerSyntaxError self, "Unknown command type -" && word 1 of tLine, tLineNumber
break
end switch
if tNeedPush then
put "push" into tComponentCommands[tComponentCommandCount]["type"]
put tLine into tComponentCommands[tComponentCommandCount]["line content"]
put tLineNumber into tComponentCommands[tComponentCommandCount]["line"]
add 1 to tComponentCommandCount
end if
if tCommand is an array then
put tLineNumber into tCommand["line"]
put tCommand into tComponentCommands[tComponentCommandCount]
add 1 to tComponentCommandCount
end if
end repeat
if tComponentName is not empty then
-- Make sure we unwind the context stack for the last component
repeat while tContext is not empty
delete the last line of tContext
put "pop" into tComponentCommands[tComponentCommandCount]["type"]
add 1 to tComponentCommandCount
end repeat
put tComponentCommands into self[tComponentType][tComponentName]
end if
end compilerParse
################################################################################
private command compilerRun @self, pInstaller
if pInstaller is not among the keys of self["installers"] then
compilerExecutionError self, "Unknown installer - '" & pInstaller & "'"
end if
compilerExecute self, self["installers"][pInstaller]
end compilerRun
private command compilerExecute @self, pCommands
local tCommand
repeat with tCommandIndex = 0 to (the number of elements of pCommands-1)
put pCommands[tCommandIndex] into tCommand
-- If we are ignoring commands at the moment (as a result of an if)
-- then skip. Note we always execute pushes and pops to ensure
-- nesting is sound.
if self["context"]["__IGNORE__"] and tCommand["type"] is not among the items of "push,pop" then
next repeat
end if
if false then
compilerProgress self, "TRACE: Op =" && tCommand["type"] && "(line" && tCommand["line"] && colon && tCommand["line content"] & ")"
end if
switch tCommand["type"]
case "push"
compilerExecutePush self, tCommand
break
case "pop"
compilerExecutePop self, tCommand
break
case "include"
compilerExecuteInclude self, tCommand
break
case "set"
compilerExecuteSet self, tCommand
break
case "copy"
compilerExecuteCopy self, tCommand
break
case "create"
compilerExecuteCreate self, tCommand
break
case "emit"
compilerExecuteEmit self, tCommand
break
case "declare"
compilerExecuteDeclare self, tCommand
break
case "if"
compilerExecuteIf self, tCommand
break
case "shortcut"
compilerExecuteShortcut self, tCommand
break
case "place"
compilerExecutePlace self, tCommand
break
case "register"
compilerExecuteRegister self, tCommand
break
case "rename"
compilerExecuteRename self, tCommand
break
case "desktop"
compilerExecuteDesktop self, tCommand
break
case "nop"
break
default
throw "Unknown command -" && tCommand["type"]
end switch
end repeat
end compilerExecute
private command compilerExecutePush @self, pCommand
get the number of elements of self["stack"]
put self["context"] into self["stack"][it]
end compilerExecutePush
private command compilerExecutePop @self, pCommand
get the number of elements of self["stack"]
if it is zero then
compilerExecutionError self, "Attempt to pop context when stack is empty (" & quote & pCommand["line content"] & quote && "on line" && pCommand["line"] & ")"
end if
put self["stack"][it - 1] into self["context"]
delete variable self["stack"][it - 1]
end compilerExecutePop
private command compilerExecuteInclude @self, pCommand
local tComponent
put compilerEvaluate(self, pCommand["name"], pCommand["line"]) into tComponent
if tComponent is not among the keys of self["components"] then
compilerExecutionError self, "Unknown component '" & tComponent & "'", pCommand["line"]
end if
compilerExecute self, self["components"][tComponent]
end compilerExecuteInclude
private command compilerExecuteSet @self, pCommand
local tValue
put compilerEvaluate(self, pCommand["value"], pCommand["line"]) into tValue
put tValue into self["context"][pCommand["variable"]]
end compilerExecuteSet
private command compilerExecuteCopy @self, pCommand
local tSource, tSourceType, tSourceFile
put compilerEvaluate(self, pCommand["source"], pCommand["line"]) into tSource
set the itemDelimiter to ":"
put item 1 of tSource into tSourceType
put item 2 to -1 of tSource into tSourceFile
set the itemDelimiter to comma
local tTarget
if pCommand["target"] is not empty then
put compilerEvaluate(self, pCommand["target"], pCommand["line"]) into tTarget
else
set the itemDelimiter to "/"
put the last item of tSourceFile into tTarget
set the itemDelimiter to comma
end if
if self["context"]["RootFolder"] is not empty then
put self["context"]["RootFolder"] & slash before tTarget
end if
local tBaseSource, tBaseSourceFile, tBaseSourceType
if pCommand["source-base"] is not empty then
put compilerEvaluate(self, pCommand["source-base"], pCommand["line"]) into tBaseSource
set the itemDelimiter to ":"
put item 1 of tBaseSource into tBaseSourceType
put item 2 to -1 of tBaseSource into tBaseSourceFile
set the itemDelimiter to comma
else
put empty into tBaseSource
put empty into tBaseSourceFile
put empty into tBaseSourceType
end if
-- If the source is unknown, throw an error
if tSourceType is not among the keys of self["sources"] then
compilerExecutionError self, "Unknown source type - '" & tSourceType & "'", pCommand["line"]
end if
-- If the source file doesn't exist, append a warning
local tSourcePath, tBaseSourcePath
put self["sources"][tSourceType] & slash & tSourceFile into tSourcePath
if self["base_sources"][tSourceType] is not empty then
put self["sources"][self["base_sources"][tSourceType]] & slash & tSourceFile into tBaseSourcePath
else if tBaseSource is not empty then
put self["sources"][tBaseSourceType] & slash & tBaseSourceFile into tBaseSourcePath
end if
-- If the source is a folder then we must check that it is a bundle,
-- otherwise it is a file. We construct the 'manifest' which, in the
-- case of a file will be one item long, but in the case of the bundle
-- will be determined by its manifest file.
local tItemManifest
if abstractThereIsAFolder(tSourcePath) then
if pCommand["class"] is not among the items of "folder,executable,rfolder" then
compilerBuildWarning self, "Item '" & tSourcePath & "' is a folder, but not of executable or folder type", pCommand["line"]
exit compilerExecuteCopy
end if
-- Simply take the manifest file from within the bundle (one assumes
-- this will be automagically processed so shouldn't be any problems).
-- The expected syntax is:
-- <class>,/<relativefile>
-- (If rfolder then we generate one)
if pCommand["class"] is among the items "executable,rfolder" then
compilerProgress self, "Creating manifest for item '" & tSourcePath & "'"
put createManifest(tSourcePath) into tItemManifest
else if abstractThereIsAFile(tSourcePath & slash & "Manifest") then
put url ("file:" & tSourcePath & slash & "Manifest") into tItemManifest
else
put pCommand["class"], empty into tItemManifest
end if
else
if not abstractThereIsAFile(tSourcePath) then
compilerExecutionError self, "Item '" & tSourcePath & "' could not be found", pCommand["line"]
end if
put pCommand["class"], empty into tItemManifest
end if
-- Work out the processing type required and any suffix required due
-- to the processing. Note here that (for each item) the various paths
-- are simply formed by concatenating items from the manifest with
-- the roots.
repeat for each line tItem in tItemManifest
local tItemClass, tItemSourcePath, tItemSourceFile, tItemTarget, tBaseItemSourcePath
if the number of items of tItem is 0 then
next repeat
end if
put item 1 of tItem into tItemClass
put tSourcePath & item 2 of tItem into tItemSourcePath
put tSourceFile & item 2 of tItem into tItemSourceFile
put tTarget & item 2 of tItem into tItemTarget
if tBaseSourcePath is not empty then
put tBaseSourcePath & item 2 of tItem into tBaseItemSourcePath
end if
if tItemClass is not "folder" and not abstractThereIsAFile(tItemSourcePath) then
compilerExecutionError self, "Bundle item '" & tItemSourcePath & "' could not be found", pCommand["line"]
end if
local tProcess, tSuffix, tManifestType
put empty into tSuffix
switch tItemClass
case "executable"
local tExeType
put sniffExecutable(abstractPinFile(tItemSourcePath)) into tExeType
if tExeType is "macosx" and tSourceType is not "ios" then
get self["context"]["TargetArchitectures"]
sort items of it ascending
put "slim-" & it into tProcess
if "i386" is among the items of it then
put "i" after tSuffix
end if
if "ppc" is among the items of it then
put "p" after tSuffix
end if
if "x86_64" is among the items of it then
put "a" after tSuffix
end if
else if tExeType is "windows" and compilerEvaluateOptVar(self, "SignExecutables") and self["options"]["sign.certificate"] is not empty then
put "sign" into tProcess
put "s" into tSuffix
else
put "none" into tProcess
end if
put "executable" into tManifestType
break
case "textfile"
put "text" into tProcess
put "txt" into tSuffix
put "file" into tManifestType
break
case "file"
case "stack"
put "none" into tProcess
put "file" into tManifestType
break
case "folder"
put empty into tProcess
put "folder" into tManifestType
break
end switch
-- If the item is not just an (empty) folder creation, then we
-- must reference it in the files list
local tItemName, tBaseItemName
if tManifestType is not "folder" then
-- Compute the item name
put tSourceType & slash & tItemSourceFile into tItemName
if tSuffix is not empty then
put "#" & tSuffix after tItemName
end if
-- Add an entry to the file list (if not a folder)
put tItemName into self["files"][tProcess & ":" & tItemSourcePath]
if tBaseSourceFile is not empty then
-- If this was a 'based' clause then use the right files
put tBaseSourceType & slash & tBaseSourceFile into tBaseItemName
put tProcess & ":" & tBaseItemSourcePath into self["base_files"][tProcess & ":" & tItemSourcePath]
else if there is a file tBaseItemSourcePath then
-- If the file exists in the base source path, then we construct a base item name
put self["base_sources"][tSourceType] & slash & tItemSourceFile into tBaseItemName
if tSuffix is not empty then
put "#" & tSuffix after tBaseItemName
end if
-- Link the new file to the base file
put tProcess & ":" & tBaseItemSourcePath into self["base_files"][tProcess & ":" & tItemSourcePath]
else if tProcess is "sign" then
-- If we are signing, and we aren't basing on an older version then we base the signed item
-- on the unsigned one.
put "none:" & tItemSourcePath into self["base_files"][tProcess & ":" & tItemSourcePath]
put tSourceType & slash & tItemSourceFile into tBaseItemName
else if tProcess is "slim-i386" or tProcess is "slim-ppc" then
-- If we are slimming to i386, then base it on i386+ppc
put "slim-i386,ppc:" & tItemSourcePath into self["base_files"][tProcess & ":" & tItemSourcePath]
put tSourceType & slash & tItemSourceFile & "#ip" into tBaseItemName
else
put empty into tBaseItemName
end if
else
put empty into tItemName
put empty into tBaseItemName
end if
-- Add an entry to the manifest
put tManifestType & tab & tItemTarget & tab & tItemName & tab & tBaseItemName & return after self["manifest"]
end repeat
end compilerExecuteCopy
private command compilerExecuteCreate @self, pCommand
-- Work out the target path of the new folder
local tTarget
put compilerEvaluate(self, pCommand["target"], pCommand["line"]) into tTarget
if self["context"]["RootFolder"] is not empty then
put self["context"]["RootFolder"] & slash before tTarget
end if
-- Add an entry to the manifest
put "folder" & tab & tTarget & return after self["manifest"]
end compilerExecuteCreate
private command compilerExecuteEmit @self, pCommand
local tTarget
put compilerEvaluate(self, pCommand["target"], pCommand["line"]) into tTarget
if self["context"]["RootFolder"] is not empty then
put self["context"]["RootFolder"] & slash before tTarget
end if
local tClass
put pCommand["class"] into tClass
local tIndex
put the number of elements in self["derived"] + 1 into tIndex
put "derived/" & tClass & "-" & tIndex & ".txt" into self["derived"][tIndex]["name"]
put "text" into self["derived"][tIndex]["type"]
-- allow emitting a a variable to a file
switch tClass
case "dbdriver"
case "external"