Bazel: genrule to touch (create) two files - bazel

I'm trying to create a genrule that works both on windows and unix that creates two files if they don't exist yet.
for a single file, the following works:
genrule(
name = "create_files",
outs = ["path/file1.h"],
cmd_bat = "echo. >> $#",
cmd = "touch $#",
)
but now I'd like to create two files, the following does not seem to work:
genrule(
name = "create_files",
outs = ["path/file1.h",
"path/file2.h"],
cmd_bat = "echo. >> $(OUTS)[0] && echo. >> $(OUTS)[1]",
cmd = "touch $(OUTS)[0] && touch $(OUTS)[1]",
)
I'm getting the error
declared output '...path/file2.h' was not created by genrule.
How can I do that properly?

The problem is with the way variable substitution works and the way arrays work in the shell.
In
cmd = "touch $(OUTS)[0] && touch $(OUTS)[1]"
Bazel replaces $(OUTS) with the paths of the output files, using space for the delimiter. See https://docs.bazel.build/versions/master/be/make-variables.html
So the command becomes
touch path/file1.h path/file2.h[0] && touch path/file1.h path/file2.h[1]
So you're getting path/file1.h touched twice, and two extraneous files.
To make this work, all you need is:
cmd = "touch $(OUTS)"
I'm not sure how to do this on Windows for cmd_bat though. I'm not aware of a built-in tool that can create multiple empty files like touch. You might need to loop over $(OUTS) and do type NUL > file or something similar.

Related

Instantiating a Bazel macro twice with same generated output file

Suppose I have a Bazel macro that is using a generator rule to generate an output file given an input file:
def my_generator(
name,
input_file,
output_file,
**kwargs):
args = []
args.extend(["--arg1", "$(location %s)" % output_file])
args.extend(["arg2", "$(locations %s)" % input_file])
cmd_params = " ".join(args)
native.genrule(
name = name,
srcs = [input_file],
outs = [output_file],
cmd = "python $(location //path/to:target_generator) %s" % cmd_params,
tools = ["/path/to/tool:mytool"],
)
Then I was previously using this macro as:
my_generator(
name = "gen1",
input_file = ":targetToGeneratetextFile",
output_file = "outputfile.txt",
visibility = ["//myproject/oath/to/current/package/test:__subpackages__"],
)
where a target is passed as input_file. This was working.
Then I wanted to reuse it with a different input but to generate the same output, where the input is now a file within the project but in another folder.
my_generator(
name = "gen2",
input_file = "//path/to/the/file/realFile.txt",
output_file = "outputfile.txt",
visibility = ["//myproject/oath/to/current/package/test:__subpackages__"],
)
I am getting two errors in this way:
For how it is, Bazel cannot find the realFile.txt: it tries to read it as a target:
no such package '//path/to/the/file/realFile.txt': BUILD file not found in any of the following directories. Add a BUILD file to a directory to mark it as a package
If I copy the file in the current package folder, it is able to read it.
Bazel is complaining that gen1 and gen2 are writing/overwriting the same output file outputfile.txt:
Error in genrule: generated file 'outputfile.txt' in rule 'gen2' conflicts with existing generated file from rule 'gen1', defined at ...
How can I solve these issues?
I think that the problem is that these two calls are both executed, whereas I would like them to be executed depending on some target, i.e., target A needs only run gen1 and target B gen2 exclusively. I do not if that is possible but for example moving each of these call inside the target they belong to might be a solution that avoids this issue.
EDIT
I was thinking as solution to do something like:
my_generator(
name = "gen2",
input_file = select({
":opt1": [":targetToGeneratetextFile"],
":opt2": ["realTextFile.txt"],
"//conditions:default": [":targetToGeneratetextFile"],
}),
output_file = "outputfile.txt",
visibility = ["//myproject/oath/to/current/package/test:__subpackages__"],
)
with proper config_setting and then call it from the target with the proper flag but I am getting the error:
expected value of type 'string' for element 0 of attribute 'srcs' in 'genrule' rule, but got select({":opt1": [":targetToGeneratetextFile"], ":opt2": ["realTextFile.txt"],"//conditions:default": [":targetToGeneratetextFile"],
})
The label //path/to/the/file/realFile.txt is shorthand for //path/to/the/file/realFile.txt:realFile.txt, aka <repository root>/path/to/the/file/realFile.txt/realFile.txt. Depending on where the deepest-nested folder with a BUILD file is (which determines the package), you're looking for something like //path/to/the/file:realFile.txt or //path/to:the/file/realFile.txt instead.
You can't have two rules which write the same file, because then Bazel can't tell which way to build it if you bazel build the file. Some alternatives:
Put them in separate packages (aka separate folders with BUILD files)
Name them differently, like gen1_outputfile.txt and gen2_outputfile.txt, or gen1/outputfile.txt and gen2/outputfile.txt. You could automate this in the macro like srcs = [name + '/outputfile.txt'].
Use a single rule to generate it with an appropriate select, like your edit.
With the select, you're trying to create something like this:
genrule(
srcs = select({..., "//conditions:default": [":targetToGeneratetextFile"]}),
...
)
but as written you have this instead:
genrule(
srcs = [select({..., "//conditions:default": [":targetToGeneratetextFile"]})],
...
)
Effectively, between the list in the select's value and the macro body, you're creating a nested list. I would change your macro argument to input_files and then do srcs = input_files in the body, so the caller of the macro can bundle things into lists as desired.

How to generate cc_library from an output directory from a genrule?

I have a binary that takes as input a single file and produces an unknown number of header and source C++ files into a single directory. I would like to be able to write a target like:
x_library(
name = "my_x_library",
src = "source.x",
)
where x_library is a macro that ultimately produces the cc_library from the output files. However, I can't bundle all the output files inside the rule implementation or inside the macro. I tried this answer but it doesn't seem to work anymore.
What's the common solution to this problem? Is it possible at all?
Small example of a macro using a genrule (not a huge fan) to get one C file and one header and provide them as a cc_library:
def x_library(name, src):
srcfile = "{}.c".format(name)
hdrfile = "{}.h".format(name)
native.genrule(
name = "files_{}".format(name),
srcs = [src],
outs = [srcfile, hdrfile],
cmd = "./generator.sh $< $(OUTS)",
tools = ["generator.sh"],
)
native.cc_library(
name = name,
srcs = [srcfile],
hdrs = [hdrfile],
)
Used it like this then:
load(":myfile.bzl", "x_library")
x_library(
name = "my_x_library",
src = "source.x",
)
cc_binary(
name = "tgt",
srcs = ["mysrc.c"],
deps = ["my_x_library"],
)
You should be able to extend that with any number of files (and for C++ content; IIRC the suffices are use for automagic decision how to call the tools) as long as your generator input -> generated content is known and stable (generally a good thing for a build). Otherwise you can no longer use genrule as you need your custom rule (probably a good thing anyways) to use TreeArtifact as described in the linked answer. Or two, one with .cc suffix and one with .hh so that you can pass them to cc_library.

How do I unzip a file in bazel properly if I don't know the contents of the zip?

I was to define a rule that unzips a given zip file. However, I don't know the contents of the zip, so I cannot specify outs in a genrule, for example. This seems like a common problem, and googling around leads me to people who have encountered similar scenarios, but I haven't yet seen a specific example of how to solve this.
I want something like:
genrule(
name="unzip",
src="file.zip",
outs=glob(["**"]), # except you're not allowed to use glob here
cmd = "unzip $(location file)",
)
You could use a Workspace Rule to create a BUILD file for the zip that globs everything.
Something like this in your WORKSPACE file:
new_http_archive(
name = "my_zip",
url = "http://example.com/my_zip.zip",
build_file_content = """
filegroup(
name = "srcs",
srcs = glob(["*"]),
visibility = ["//visibility:public"]
)
"""
)
Then from a BUILD file you can reference this as an input using #my_zip//:srcs

How do you duplicate a file in XCode?

Anyone know a good solution?
So far I have not found a better way than using File>New file and then copying contents from old file to new.
You can probably duplicate in Finder and re-import but that's almost same amount of work: switching to finder, duplicate, import new files.
Doing this with one class is not so hard, but what to do if you need to generate 10+ similar Classes based on superclass.
In Eclipse you select file and then copy/paste it in same folder. In finder there's Duplicate.
There's a menu Edit > Duplicate. But it's ALWAYS disabled. I tried selecting various files, classes, methods. It's still disabled.
In XCode 4.2 (I know this is an old question) there is Duplicate under the File menu.
Select the file (you can select multiple files but it doesn't appear to do anything useful) in the Project Navigator and then File->Duplicate. Hooray!
In Xcode 4.5 we can duplicate using File-> Duplicate or cmd + shift + S
"Duplicate" is enabled for targets in XCode (pretty much nothing else that I know of).
If you have a substantial number of subclasses with the same starting point to replicate, why not make a class template from it? Then you can just use file->New to make new instances. It's fairly quick to do.
This is probably the simplest example:
http://www.macresearch.org/custom_xcode_templates
Otherwise, I'd simply duplicate the files in Finder as many times as you need, name them, and drag them into XCode en-masse.
Careful!
When you use duplicate ( CMD + Shift + S ) - Xcode have a problem with indexing headers.
Also when u want to make a refactoring it can be next error window:
So there a couple of ways what to do, to fix that.
Delete derived data from menu Window > Projects. Restart Xcode.
Product > Clean
You could use "Save As..."; you'd still have to go back and re-add the original files to the project, though.
It wouldn't be such a bad way to do a bunch of related classes, though: edit file, Save As "class2", edit file, Save As "class3", etc., then "Add Existing Files" and re-add all of the files but the last to your project.
I use the following perl script to duplicate a file pair in the Terminal. You give it the base name of the original and new file, and it copies the header and implementation (c/cpp/m/mm) file, then replaces all occurances of the base name with the new name, then adds them to subversion. You still have to add the new files in to Xcode and adjust the creation date in the comment (I've got a Keyboard Maestro macro for that), but its quicker than doing a lot of the steps manually. I operate with a Terminal window and four tabs pre-set to the Project, Source, Resources, and English.lproj directory which gives quick access for a lot of operations.
#!/usr/bin/perl
use lib "$ENV{HOME}/perl";
use warnings;
use strict;
our $cp = '/bin/cp';
our $svn = '/usr/bin/svn';
our $perl = '/usr/bin/perl';
our $source = shift;
our $add = 1;
if ( $source =~ m!^-! ) {
if ( $source eq '-a' || $source eq '--add' ) {
$add = 1;
$source = shift;
} elsif ( $source eq '-A' || $source eq '--noadd' ) {
$add = undef;
$source = shift;
} else {
die "Bad arg $source";
}
}
our $dest = shift;
die "Bad source $source" unless $source =~ m!^(.*/)?[A-Za-z0-9]+$!;
die "Bad dest $dest" unless $dest =~ m!^(.*/)?[A-Za-z0-9]+$!;
my $cpp;
$cpp = 'c' if ( -e "$source.c" );
$cpp = 'cpp' if ( -e "$source.cpp" );
$cpp = 'mm' if ( -e "$source.mm" );
$cpp = 'm' if ( -e "$source.m" );
die "Missing source $source" unless -e "$source.h" && -e "$source.$cpp";
die "Existing dest $dest" if -e "$dest.h" && -e "$dest.$cpp";
our $sourcename = $source; $sourcename =~ s!.*/!!;
our $destname = $dest; $destname =~ s!.*/!!;
print "cp $source.h $dest.h\n";
system( $cp, "$source.h", "$dest.h" );
print "s/$sourcename/$destname in $dest.h\n";
system( $perl, '-p', '-i', '-e', "s/$sourcename/$destname/g", "$dest.h" );
print "cp $source.$cpp $dest.$cpp\n";
system( $cp, "$source.$cpp", "$dest.$cpp" );
print "s/$sourcename/$destname in $dest.$cpp\n";
system( $perl, '-p', '-i', '-e', "s/$sourcename/$destname/g", "$dest.$cpp" );
if ( $add ) {
print "svn add $dest.$cpp $dest.h\n";
system( $svn, 'add', "$dest.$cpp", "$dest.h" );
}
In my case, one of my folder changed from one place to another place.
I have "Home" folder in Controller folder, but unfortunately it's moved from Controller folder to Manager folder.
I checked many times everything fine, but I'm getting Command PrecompileSwiftBridgingHeader failed with a nonzero exit code
But after 2 hours i realised, my folder structure changed.

How to get folder path from file path with CMD

I need path to the folder that contains cmd file.
With %0 I can get the file name. But how to get the folder name?
c:\temp\test.cmd >> test.cmd
P.S. My current directory != folder of the script.
For the folder name and drive, you can use:
echo %~dp0
You can get a lot more information using different modifiers:
%~I - expands %I removing any surrounding quotes (")
%~fI - expands %I to a fully qualified path name
%~dI - expands %I to a drive letter only
%~pI - expands %I to a path only
%~nI - expands %I to a file name only
%~xI - expands %I to a file extension only
%~sI - expanded path contains short names only
%~aI - expands %I to file attributes of file
%~tI - expands %I to date/time of file
%~zI - expands %I to size of file
The modifiers can be combined to get compound results:
%~dpI - expands %I to a drive letter and path only
%~nxI - expands %I to a file name and extension only
%~fsI - expands %I to a full path name with short names only
This is a copy paste from the "for /?" command on the prompt.
Related
Top 10 DOS Batch tips (Yes, DOS Batch...) shows batchparams.bat (link to source as a gist):
C:\Temp>batchparams.bat c:\windows\notepad.exe
%~1 = c:\windows\notepad.exe
%~f1 = c:\WINDOWS\NOTEPAD.EXE
%~d1 = c:
%~p1 = \WINDOWS\
%~n1 = NOTEPAD
%~x1 = .EXE
%~s1 = c:\WINDOWS\NOTEPAD.EXE
%~a1 = --a------
%~t1 = 08/25/2005 01:50 AM
%~z1 = 17920
%~$PATHATH:1 =
%~dp1 = c:\WINDOWS\
%~nx1 = NOTEPAD.EXE
%~dp$PATH:1 = c:\WINDOWS\
%~ftza1 = --a------ 08/25/2005 01:50 AM 17920 c:\WINDOWS\NOTEPAD.EXE
The accepted answer is helpful, but it isn't immediately obvious how to retrieve a filename from a path if you are NOT using passed in values. I was able to work this out from this thread, but in case others aren't so lucky, here is how it is done:
#echo off
setlocal enabledelayedexpansion enableextensions
set myPath=C:\Somewhere\Somewhere\SomeFile.txt
call :file_name_from_path result !myPath!
echo %result%
goto :eof
:file_name_from_path <resultVar> <pathVar>
(
set "%~1=%~nx2"
exit /b
)
:eof
endlocal
Now the :file_name_from_path function can be used anywhere to retrieve the value, not just for passed in arguments. This can be extremely helpful if the arguments can be passed into the file in an indeterminate order or the path isn't passed into the file at all.
In order to assign these to variables, be sure not to add spaces in front or after the equals sign:
set filepath=%~dp1
set filename=%~nx1
Then you should have no issues.
In case anyone wants an alternative method...
If it is the last subdirectory in the path, you can use this one-liner:
cd "c:\directory\subdirectory\filename.exe\..\.." && dir /ad /b /s
This would return the following:
c:\directory\subdirectory
The .... drops back to the previous directory.
/ad shows only directories
/b is a bare format listing
/s includes all subdirectories. This is used to get the full path of the directory to print.
I had same problem in my loop where i wanted to extract zip files in the same directory and then delete the zip file. The problem was that 7z requires the output folder, so i had to obtain the folder path of each file. Here is my solution:
FOR /F "usebackq tokens=1" %%i IN (`DIR /S/B *.zip` ) DO (
7z.exe x %%i -aoa -o%%i\..
)
%%i was a full filename path and %ii\.. simply returns the parent folder.
hope it helps.
In case the accepted answer by Wadih didn't work for you, try echo %CD%
This was put together with some edited example cmd
#Echo off
Echo ********************************************************
Echo * ZIP Folder Backup using 7Zip *
Echo * Usage: Source Folder, Destination Drive Letter *
Echo * Source Folder will be Zipped to Destination\Backups *
Echo ********************************************************
Echo off
set year=%date:~-4,4%
set month=%date:~-10,2%
set day=%date:~-7,2%
set hour=%time:~-11,2%
set hour=%hour: =0%
set min=%time:~-8,2%
SET /P src=Source Folder to Backup:
SET source=%src%\*
call :file_name_from_path nam %src%
SET /P destination=Backup Drive Letter:
set zipfilename=%nam%.%year%.%month%.%day%.%hour%%min%.zip
set dest="%destination%:\Backups\%zipfilename%"
set AppExePath="%ProgramFiles(x86)%\7-Zip\7z.exe"
if not exist %AppExePath% set AppExePath="%ProgramFiles%\7-Zip\7z.exe"
if not exist %AppExePath% goto notInstalled
echo Backing up %source% to %dest%
%AppExePath% a -r -tzip %dest% %source%
echo %source% backed up to %dest% is complete!
TIMEOUT 5
exit;
:file_name_from_path <resultVar> <pathVar>
(
set "%~1=%~nx2"
exit /b
)
:notInstalled
echo Can not find 7-Zip, please install it from:
echo http://7-zip.org/
:end
PAUSE
IMHO the simplest yet most powerful method to get the full path of a file is:
Start Notepad
Copy and Paste the following text:
#echo %1 | clip 3
Save the file as "CopyAsPath.bat"
Now you can either
drag a file over that batch
or
copy it into your SendTo menu folder (%USERPROFILE%\AppData\Roaming\Microsoft\Windows\SendTo)
Whether you follow the option 1 or the option 2, one millisecond later you can either
"press Ctrl-V"
or
use "Right Mouse Click -> Paste"
to paste the full path+filename wherever you want.
Simple, powerful, and without using any external Windows tool.

Resources